<script setup lang="ts">
import { ElMessage, ElMessageBox } from "element-plus";
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";

import AdminLoginView from "./components/AdminLoginView.vue";
import AdminPagination from "./components/AdminPagination.vue";
import AgentManagementView from "./components/AgentManagementView.vue";
import KnowledgeManagementView from "./components/KnowledgeManagementView.vue";
import TableRowActions from "./components/TableRowActions.vue";
import { systemSettingDefinitions, systemSettingSections, type SystemSettingValue } from "./config/systemSettings";
import { api, clearToken, getToken, saveToken } from "./services/api";
import type {
  AdminProfile,
  AdminUser,
  AdminUserDetail,
  AiLogRecord,
  ChatDetail,
  ChatRecord,
  DashboardStats,
  EntitlementPlan,
  KnowledgeItem,
  RetrievalLogItem,
  AttentionItem,
  ModelItem,
  QuestionInsightSummary,
  SystemConfigItem,
  UserImportResult,
} from "./types/api";

const admin = ref<AdminProfile | null>(null);
const activeMenu = ref("dashboard");
const loading = ref(false);

const stats = ref<DashboardStats | null>(null);
const storageStats = ref<Record<string, any> | null>(null);
const storageLoading = ref(false);
const cleanupBefore = ref("");
const retentionDays = ref<number | null>(90);
const retentionState = ref<{ lastRunAt: string | null; lastResult: string | null } | null>(null);
const dashboardFilters = reactive({
  start: "",
  end: "",
});
const users = ref<AdminUser[]>([]);
const selectedUserDetail = ref<AdminUserDetail | null>(null);
const userDetailOpen = ref(false);
const userDetailLoading = ref(false);
const userReportGenerating = ref("");
const userKeyword = ref("");
const entitlementPlans = ref<EntitlementPlan[]>([]);
const editingEntitlementPlanId = ref<number | null>(null);
const models = ref<ModelItem[]>([]);
const configs = ref<SystemConfigItem[]>([]);
const chats = ref<ChatRecord[]>([]);
const aiLogs = ref<AiLogRecord[]>([]);
const operationLogs = ref<Record<string, unknown>[]>([]);
const questionInsights = ref<QuestionInsightSummary | null>(null);
const retrievalLogs = ref<RetrievalLogItem[]>([]);
const attentionRecords = ref<AttentionItem[]>([]);
const selectedRetrievalLog = ref<Record<string, unknown> | null>(null);
const retrievalDetailOpen = ref(false);
const previewKnowledgeId = ref<number | null>(null);
const chatDetail = ref<ChatDetail | null>(null);
const chatDetailOpen = ref(false);
const chatDetailLoading = ref(false);
const chatMessagesLoading = ref(false);
const chatDetailSessionId = ref<number | null>(null);
const chatDetailBodyRef = ref<HTMLElement | null>(null);
const selectedAiLog = ref<AiLogRecord | null>(null);
const aiLogDetailOpen = ref(false);
const recordTab = ref("chats");
const pagers = reactive({
  users: { page: 1, pageSize: 20, total: 0 },
  chats: { page: 1, pageSize: 20, total: 0 },
  aiLogs: { page: 1, pageSize: 20, total: 0 },
  operationLogs: { page: 1, pageSize: 20, total: 0 },
  questionInsights: { page: 1, pageSize: 20, total: 0 },
  retrievals: { page: 1, pageSize: 20, total: 0 },
  attention: { page: 1, pageSize: 20, total: 0 },
});
const chatDetailMessagePager = reactive({ page: 1, pageSize: 20, total: 0 });
const editingModelId = ref<number | null>(null);
const editingKnowledgeId = ref<number | null>(null);
const batchNodeToken = ref("");
const batchImporting = ref(false);

const studentForm = reactive({
  phone: "",
  name: "",
  nickname: "",
  status: 1,
  dailyChatLimit: 100,
  expiredAt: "",
});
const studentImportFile = ref<File | null>(null);
const studentImportInput = ref<HTMLInputElement | null>(null);
const studentImportResult = ref<UserImportResult | null>(null);

const entitlementPlanForm = reactive({
  name: "",
  planType: "basic" as "basic" | "deep" | "addon" | "teacher",
  description: "",
  validityDays: null as number | null,
  monthlyTopicLimit: 30 as number | null,
  enableGrowthProfile: 0,
  enablePeriodicReports: 0,
  allowHelpCard: 1,
  allowShareDraft: 1,
  deductQuota: 1,
  status: 1,
  sortOrder: 10,
});

const knowledgeForm = reactive({
  name: "",
  feishuSpaceId: "",
  feishuNodeId: "",
  status: 1,
  remark: "",
});

const modelForm = reactive({
  provider: "openai-compatible",
  displayName: "",
  apiType: "openai_compatible",
  modelName: "",
  baseUrl: "https://api.openai.com/v1",
  apiUrl: "",
  apiKey: "",
  authType: "bearer",
  apiVersion: "",
  temperature: null as number | null,
  topP: null as number | null,
  topK: null as number | null,
  presencePenalty: null as number | null,
  frequencyPenalty: null as number | null,
  maxToken: 8192 as number | null,
  contextWindow: null as number | null,
  streamEnabled: 1,
  responseFormat: "",
  extraParams: "",
  remark: "",
  timeoutSecond: 30,
  inputPricePer1k: null as number | null,
  outputPricePer1k: null as number | null,
  currency: "CNY",
  usageScenarios: "",
  allowSummary: 1,
  allowReport: 1,
  allowFixedInfo: 1,
  allowDeepChat: 1,
});

const quickModelForm = reactive({
  provider: "deepseek",
  modelName: "deepseek-v4-pro",
  apiKey: "",
});

const quickModelProviders = [
  {
    label: "DeepSeek",
    provider: "deepseek",
    apiType: "openai_compatible",
    baseUrl: "https://api.deepseek.com",
    authType: "bearer",
    apiVersion: "",
    temperature: null,
    topP: null,
    maxToken: null,
    contextWindow: null,
    models: [
      { label: "deepseek-v4-pro", value: "deepseek-v4-pro" },
      { label: "deepseek-v4-flash", value: "deepseek-v4-flash" },
    ],
  },
  {
    label: "MiniMax",
    provider: "minimax",
    apiType: "openai_compatible",
    baseUrl: "https://api.minimaxi.com/v1",
    authType: "bearer",
    apiVersion: "",
    temperature: null,
    topP: null,
    maxToken: null,
    contextWindow: null,
    models: [
      { label: "MiniMax-Text-01", value: "MiniMax-Text-01" },
      { label: "MiniMax-M3", value: "MiniMax-M3" },
    ],
  },
] as const;

const protocolPresets = [
  {
    label: "OpenAI 兼容协议",
    provider: "openai-compatible",
    apiType: "openai_compatible",
    baseUrl: "https://api.openai.com/v1",
    apiUrl: "",
    authType: "bearer",
  },
  {
    label: "Anthropic Messages 协议",
    provider: "anthropic-compatible",
    apiType: "anthropic_messages",
    baseUrl: "https://api.anthropic.com",
    apiUrl: "",
    authType: "api_key",
    apiVersion: "2023-06-01",
    temperature: null,
    topP: null,
    topK: null,
  },
];

const nullableModelFields = [
  "temperature",
  "topP",
  "topK",
  "presencePenalty",
  "frequencyPenalty",
  "contextWindow",
  "responseFormat",
  "extraParams",
  "remark",
  "inputPricePer1k",
  "outputPricePer1k",
  "usageScenarios",
] as const;

const systemSettingValues = reactive<Record<string, SystemSettingValue>>({});
systemSettingDefinitions.forEach((setting) => {
  systemSettingValues[setting.key] = setting.defaultValue;
});

const chatFilters = reactive({
  keyword: "",
  userId: undefined as number | undefined,
  status: "",
  dateFrom: "",
  dateTo: "",
});

const questionInsightFilters = reactive({
  dateFrom: "",
  dateTo: "",
  minCount: 2,
  maxMessages: 5000,
});

watch(recordTab, async (tab) => {
  if (activeMenu.value === "records") await loadRecordTab(tab);
});

onMounted(async () => {
  if (!getToken()) return;
  try {
    admin.value = await api.profile();
    await loadCurrentMenu();
  } catch {
    clearToken();
  }
});

async function login(credentials: { username: string; password: string }) {
  loading.value = true;
  try {
    const result = await api.login(credentials.username, credentials.password);
    saveToken(result.token);
    admin.value = result.admin;
    await loadCurrentMenu();
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "登录失败");
  } finally {
    loading.value = false;
  }
}

async function logout() {
  try {
    await api.logout();
  } finally {
    clearToken();
    admin.value = null;
  }
}

async function switchMenu(menu: string) {
  activeMenu.value = menu;
  await loadCurrentMenu();
}

async function previewKnowledge(knowledgeId: number) {
  previewKnowledgeId.value = knowledgeId;
  await switchMenu("prompt");
}

async function loadDashboard() {
  stats.value = await api.dashboard(dashboardFilters.start, dashboardFilters.end);
  storageLoading.value = true;
  api.storageStats().then((value) => { storageStats.value = value; }).catch(() => { storageStats.value = null; }).finally(() => { storageLoading.value = false; });
}

async function refreshStorage() { storageLoading.value = true; try { storageStats.value = await api.storageStats(true); ElMessage.success("存储统计已刷新"); } finally { storageLoading.value = false; } }
function formatBytes(value: unknown) { if (typeof value !== "number") return "无法统计"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = value, i = 0; while (size >= 1024 && i < units.length - 1) { size /= 1024; i += 1; } return `${size.toFixed(i ? 2 : 0)} ${units[i]}`; }
function formatMoney(value?: number | null, currency = "CNY") { if (typeof value !== "number") return "-"; return `${currency || "CNY"} ${value.toFixed(6)}`; }
async function cleanupRetrievalLogs() { if (!cleanupBefore.value) return ElMessage.warning("请选择清理日期"); const estimate = await api.estimateRetrievalCleanup(cleanupBefore.value); await ElMessageBox.confirm(`预计清理 ${estimate.estimatedCount} 条检索日志及关联明细，操作日志不会被删除。`, "确认清理", { type: "warning" }); const result = await api.cleanupRetrievalLogs(cleanupBefore.value); ElMessage.success(`已清理 ${result.deleted} 条日志`); await loadCurrentMenu(); }
async function saveRetention() { await api.saveRetrievalRetention(retentionDays.value); ElMessage.success(retentionDays.value == null ? "已设为永久保留" : `已设为保留 ${retentionDays.value} 天`); await loadCurrentMenu(); }

async function loadCurrentMenu() {
  loading.value = true;
  try {
    if (activeMenu.value === "dashboard") await loadDashboard();
    if (activeMenu.value === "users") { await loadEntitlementPlans(); await loadUsers(pagers.users.page, pagers.users.pageSize); }
    if (activeMenu.value === "entitlements") await loadEntitlementPlans();
    if (activeMenu.value === "models") models.value = await api.models();
    if (activeMenu.value === "configs") {
      configs.value = await api.configs();
      hydrateSystemSettings();
    }
    if (activeMenu.value === "records") await loadRecordTab(recordTab.value);
    if (activeMenu.value === "retrievals") { const [logs, retention] = await Promise.all([api.retrievalLogs({ page: pagers.retrievals.page, pageSize: pagers.retrievals.pageSize }), api.retrievalRetention()]); retrievalLogs.value = logs.items; Object.assign(pagers.retrievals, { page: logs.page, pageSize: logs.pageSize, total: logs.total }); retentionDays.value = retention.retentionDays; retentionState.value = retention; }
    if (activeMenu.value === "attention") await loadAttention(pagers.attention.page, pagers.attention.pageSize);
  } finally {
    loading.value = false;
  }
}

async function loadUsers(page = 1, pageSize = pagers.users.pageSize) {
  const result = await api.users({ keyword: userKeyword.value, page, pageSize });
  users.value = result.items;
  Object.assign(pagers.users, { page: result.page, pageSize: result.pageSize, total: result.total });
}

async function loadEntitlementPlans() {
  entitlementPlans.value = await api.entitlementPlans(true);
}

function planTypeLabel(type: string) {
  return {
    basic: "基础版",
    deep: "深度陪伴版",
    addon: "高频加购包",
    teacher: "老师工作版",
  }[type] || type;
}

async function saveEntitlementPlan() {
  if (!entitlementPlanForm.name.trim()) {
    ElMessage.warning("请填写权益版本名称");
    return;
  }
  const payload = { ...entitlementPlanForm };
  if (editingEntitlementPlanId.value) {
    await api.updateEntitlementPlan(editingEntitlementPlanId.value, payload);
    ElMessage.success("权益版本已更新");
  } else {
    await api.createEntitlementPlan(payload);
    ElMessage.success("权益版本已新增");
  }
  resetEntitlementPlanForm();
  await loadEntitlementPlans();
}

function editEntitlementPlan(row: EntitlementPlan) {
  editingEntitlementPlanId.value = row.id;
  Object.assign(entitlementPlanForm, {
    name: row.name,
    planType: row.planType,
    description: row.description ?? "",
    validityDays: row.validityDays ?? null,
    monthlyTopicLimit: row.monthlyTopicLimit ?? null,
    enableGrowthProfile: row.enableGrowthProfile ? 1 : 0,
    enablePeriodicReports: row.enablePeriodicReports ? 1 : 0,
    allowHelpCard: row.allowHelpCard ? 1 : 0,
    allowShareDraft: row.allowShareDraft ? 1 : 0,
    deductQuota: row.deductQuota ? 1 : 0,
    status: row.status,
    sortOrder: row.sortOrder,
  });
}

function resetEntitlementPlanForm() {
  editingEntitlementPlanId.value = null;
  Object.assign(entitlementPlanForm, {
    name: "",
    planType: "basic",
    description: "",
    validityDays: null,
    monthlyTopicLimit: 30,
    enableGrowthProfile: 0,
    enablePeriodicReports: 0,
    allowHelpCard: 1,
    allowShareDraft: 1,
    deductQuota: 1,
    status: 1,
    sortOrder: 10,
  });
}

async function assignUserEntitlement(row: AdminUser, planId: number) {
  const entitlement = await api.assignUserEntitlement(row.id, { planId });
  row.entitlement = entitlement;
  ElMessage.success("用户权益已更新");
}

async function openUserDetail(row: AdminUser) {
  userDetailOpen.value = true;
  selectedUserDetail.value = null;
  userDetailLoading.value = true;
  try {
    selectedUserDetail.value = await api.userDetail(row.id);
  } finally {
    userDetailLoading.value = false;
  }
}

async function generateUserReport(type: "weekly" | "monthly" | "stage") {
  if (!selectedUserDetail.value) return;
  userReportGenerating.value = type;
  try {
    await api.generateUserReport(selectedUserDetail.value.user.id, { reportType: type });
    selectedUserDetail.value = await api.userDetail(selectedUserDetail.value.user.id);
    ElMessage.success("报告已生成");
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "报告生成失败");
  } finally {
    userReportGenerating.value = "";
  }
}

async function loadRecordTab(tab = recordTab.value) {
  loading.value = true;
  try {
    if (tab === "chats") {
      const result = await api.chats({ ...buildChatQuery(), page: pagers.chats.page, pageSize: pagers.chats.pageSize });
      chats.value = result.items; Object.assign(pagers.chats, { page: result.page, pageSize: result.pageSize, total: result.total });
    } else if (tab === "aiLogs") {
      const result = await api.aiLogs({ page: pagers.aiLogs.page, pageSize: pagers.aiLogs.pageSize });
      aiLogs.value = result.items; Object.assign(pagers.aiLogs, { page: result.page, pageSize: result.pageSize, total: result.total });
    } else if (tab === "operationLogs") {
      const result = await api.operationLogs({ page: pagers.operationLogs.page, pageSize: pagers.operationLogs.pageSize });
      operationLogs.value = result.items; Object.assign(pagers.operationLogs, { page: result.page, pageSize: result.pageSize, total: result.total });
    } else if (tab === "questionInsights") {
      await loadQuestionInsights(pagers.questionInsights.page, pagers.questionInsights.pageSize);
    }
  } finally { loading.value = false; }
}

async function changeRecordPage(tab: "chats" | "aiLogs" | "operationLogs" | "questionInsights", page: number, pageSize: number) {
  Object.assign(pagers[tab], { page, pageSize });
  await loadRecordTab(tab);
}

async function loadQuestionInsights(page = 1, pageSize = pagers.questionInsights.pageSize) {
  const result = await api.questionInsights({
    dateFrom: formatRecordDateTime(questionInsightFilters.dateFrom, "start"),
    dateTo: formatRecordDateTime(questionInsightFilters.dateTo, "end"),
    minCount: questionInsightFilters.minCount,
    maxMessages: questionInsightFilters.maxMessages,
    page,
    pageSize,
  });
  questionInsights.value = result;
  Object.assign(pagers.questionInsights, { page: result.page, pageSize: result.pageSize, total: result.total });
}

async function loadRetrievals(page = 1, pageSize = pagers.retrievals.pageSize) {
  const result = await api.retrievalLogs({ page, pageSize });
  retrievalLogs.value = result.items; Object.assign(pagers.retrievals, { page: result.page, pageSize: result.pageSize, total: result.total });
}

async function loadAttention(page = 1, pageSize = pagers.attention.pageSize) {
  const result = await api.attentionList({ page, pageSize });
  attentionRecords.value = result.items; Object.assign(pagers.attention, { page: result.page, pageSize: result.pageSize, total: result.total });
}

async function saveUser(row: AdminUser) {
  await api.updateUser(row.id, { status: row.status, dailyChatLimit: row.dailyChatLimit });
  ElMessage.success("用户已更新");
  await loadCurrentMenu();
}

async function createStudent() {
  if (!studentForm.phone || !studentForm.name) {
    ElMessage.error("请填写手机号和姓名");
    return;
  }
  await api.createUser({
    phone: studentForm.phone,
    name: studentForm.name,
    nickname: studentForm.nickname || null,
    status: studentForm.status,
    dailyChatLimit: studentForm.dailyChatLimit,
    expiredAt: toExpiryDateTime(studentForm.expiredAt),
  });
  ElMessage.success("学员已添加");
  resetStudentForm();
  await loadCurrentMenu();
}

function toExpiryDateTime(date: string) {
  return date ? `${date}T23:59:59` : null;
}

function handleStudentImportFile(event: Event) {
  const input = event.target as HTMLInputElement;
  studentImportFile.value = input.files?.[0] ?? null;
}

async function downloadStudentTemplate() {
  await api.downloadStudentTemplate();
}

async function importStudents() {
  if (!studentImportFile.value) {
    ElMessage.error("请先选择 Excel 文件");
    return;
  }
  studentImportResult.value = await api.importUsersExcel(studentImportFile.value);
  ElMessage.success(
    `导入完成：新增 ${studentImportResult.value.created}，更新 ${studentImportResult.value.updated}，失败 ${studentImportResult.value.failed}`,
  );
  studentImportFile.value = null;
  if (studentImportInput.value) studentImportInput.value.value = "";
  await loadCurrentMenu();
}

function resetStudentForm() {
  Object.assign(studentForm, { phone: "", name: "", nickname: "", status: 1, dailyChatLimit: 100, expiredAt: "" });
}

async function deleteUser(row: AdminUser) {
  if (!window.confirm(`确认删除用户 ${row.phone} 吗？删除后前台将无法继续使用该账号。`)) return;
  await api.deleteUser(row.id);
  ElMessage.success("用户已删除");
  await loadCurrentMenu();
}

async function saveKnowledge() {
  if (!knowledgeForm.name || !knowledgeForm.feishuSpaceId || !knowledgeForm.feishuNodeId) {
    ElMessage.error("请填写知识库名称、SpaceID 和 NodeID");
    return;
  }
  if (editingKnowledgeId.value) {
    await api.updateKnowledge(editingKnowledgeId.value, { ...knowledgeForm });
    ElMessage.success("知识库已更新");
  } else {
    await api.createKnowledge({ ...knowledgeForm });
    ElMessage.success("知识库已新增");
  }
  resetKnowledgeForm();
  await loadCurrentMenu();
}

async function saveKnowledgeRow(row: KnowledgeItem) {
  await api.updateKnowledge(row.id, {
    name: row.name,
    feishuSpaceId: row.feishuSpaceId,
    feishuNodeId: row.feishuNodeId,
    status: row.status,
    remark: row.remark ?? "",
  });
  ElMessage.success("知识库状态已更新");
}

function editKnowledge(row: KnowledgeItem) {
  editingKnowledgeId.value = row.id;
  Object.assign(knowledgeForm, {
    name: row.name,
    feishuSpaceId: row.feishuSpaceId,
    feishuNodeId: row.feishuNodeId,
    status: row.status,
    remark: row.remark ?? "",
  });
}

async function deleteKnowledge(row: KnowledgeItem) {
  if (!window.confirm(`确认删除知识库 ${row.name} 吗？删除后所有用户都无法再检索该知识库。`)) return;
  await api.deleteKnowledge(row.id);
  ElMessage.success("知识库已删除");
  if (editingKnowledgeId.value === row.id) resetKnowledgeForm();
  await loadCurrentMenu();
}

function resetKnowledgeForm() {
  editingKnowledgeId.value = null;
  Object.assign(knowledgeForm, { name: "", feishuSpaceId: "", feishuNodeId: "", status: 1, remark: "" });
}

async function batchImportKnowledge() {
  const token = batchNodeToken.value.trim();
  if (!token) {
    ElMessage.warning("请输入 NodeToken");
    return;
  }
  batchImporting.value = true;
  try {
    const result = await api.importKnowledgeFromSpace({ node_token: token });
    if (result.imported > 0) {
      ElMessage.success(`导入成功：${result.name}（id=${result.id}）`);
    } else {
      ElMessage.info(`该节点已存在：${result.name}（id=${result.id}）`);
    }
    batchNodeToken.value = "";
    await loadCurrentMenu();
  } catch (e: any) {
    ElMessage.error(e.message || "导入失败");
  } finally {
    batchImporting.value = false;
  }
}

async function saveModel() {
  if (!validateModelForm()) return;
  const payload = buildModelPayload();
  if (editingModelId.value) {
    await api.updateModel(editingModelId.value, payload);
    ElMessage.success("模型已更新");
  } else {
    await api.createModel(payload);
    ElMessage.success("模型已新增");
  }
  resetModelForm();
  await loadCurrentMenu();
}

function buildModelPayload() {
  const payload = { ...modelForm } as Record<string, unknown>;
  nullableModelFields.forEach((field) => {
    if (payload[field] === undefined || payload[field] === "") {
      payload[field] = null;
    }
  });
  return payload;
}

const selectedQuickProvider = computed(() => {
  return quickModelProviders.find((item) => item.provider === quickModelForm.provider) ?? quickModelProviders[0];
});

async function quickAddModel() {
  const provider = selectedQuickProvider.value;
  if (!provider) return;
  if (!quickModelForm.apiKey) {
    ElMessage.error("请填写 API Key");
    return;
  }

  await api.createModel({
    provider: provider.provider,
    displayName: `${provider.label} ${quickModelForm.modelName}`,
    apiType: provider.apiType,
    modelName: quickModelForm.modelName,
    baseUrl: provider.baseUrl,
    apiUrl: "",
    apiKey: quickModelForm.apiKey,
    authType: provider.authType,
    apiVersion: provider.apiVersion,
    temperature: null,
    topP: null,
    topK: null,
    presencePenalty: null,
    frequencyPenalty: null,
    maxToken: 8192,
    contextWindow: null,
    streamEnabled: 1,
    responseFormat: null,
    extraParams: null,
    remark: "快速添加",
    timeoutSecond: 30,
    inputPricePer1k: null,
    outputPricePer1k: null,
    currency: "CNY",
    usageScenarios: "正式对话",
    allowSummary: 1,
    allowReport: 1,
    allowFixedInfo: 1,
    allowDeepChat: 1,
  });
  ElMessage.success(`${provider.label} 模型已新增`);
  quickModelForm.apiKey = "";
  await loadCurrentMenu();
}

function applyQuickProvider(provider: string) {
  const target = quickModelProviders.find((item) => item.provider === provider);
  if (!target) return;
  quickModelForm.modelName = target.models[0]?.value ?? "";
}

async function enableModel(id: number) {
  await api.enableModel(id);
  ElMessage.success("模型已启用");
  await loadCurrentMenu();
}

async function deleteModel(id: number) {
  try {
    await ElMessageBox.confirm("确认删除该模型？删除后不可恢复。", "删除模型", {
      confirmButtonText: "确认删除",
      cancelButtonText: "取消",
      type: "warning",
    });
  } catch {
    return;
  }
  await api.deleteModel(id);
  ElMessage.success("模型已删除");
  await loadCurrentMenu();
}

async function testModel(id: number) {
  const result = await api.testModel(id);
  if (result.ok) {
    ElMessage.success(result.message);
  } else {
    ElMessage.error(result.message);
  }
}

function applyModelPreset(label: string) {
  const preset = protocolPresets.find((item) => item.label === label);
  if (!preset) return;
  Object.assign(
    modelForm,
    {
      apiVersion: "",
      authType: "bearer",
      temperature: null,
      topP: null,
      topK: null,
      presencePenalty: null,
      frequencyPenalty: null,
      responseFormat: "",
      streamEnabled: 1,
    },
    preset,
  );
}

function editModel(row: ModelItem) {
  editingModelId.value = row.id;
  Object.assign(modelForm, {
    provider: row.provider,
    displayName: row.displayName ?? "",
    apiType: row.apiType,
    modelName: row.modelName,
    baseUrl: row.baseUrl ?? "",
    apiUrl: row.apiUrl,
    apiKey: "",
    authType: row.authType,
    apiVersion: row.apiVersion ?? "",
    temperature: row.temperature ?? null,
    topP: row.topP ?? null,
    topK: row.topK ?? null,
    presencePenalty: row.presencePenalty ?? null,
    frequencyPenalty: row.frequencyPenalty ?? null,
    maxToken: row.maxToken ?? 8192,
    contextWindow: row.contextWindow ?? null,
    streamEnabled: row.streamEnabled,
    responseFormat: row.responseFormat ?? "",
    extraParams: row.extraParams ?? "",
    remark: row.remark ?? "",
    timeoutSecond: row.timeoutSecond,
    inputPricePer1k: row.inputPricePer1k ?? null,
    outputPricePer1k: row.outputPricePer1k ?? null,
    currency: row.currency || "CNY",
    usageScenarios: row.usageScenarios ?? "",
    allowSummary: row.allowSummary ?? 1,
    allowReport: row.allowReport ?? 1,
    allowFixedInfo: row.allowFixedInfo ?? 1,
    allowDeepChat: row.allowDeepChat ?? 1,
  });
}

function resetModelForm() {
  editingModelId.value = null;
  Object.assign(modelForm, {
    provider: "openai-compatible",
    displayName: "",
    apiType: "openai_compatible",
    modelName: "",
    baseUrl: "https://api.openai.com/v1",
    apiUrl: "",
    apiKey: "",
    authType: "bearer",
    apiVersion: "",
    temperature: null,
    topP: null,
    topK: null,
    presencePenalty: null,
    frequencyPenalty: null,
    maxToken: 8192,
    contextWindow: null,
    streamEnabled: 1,
    responseFormat: "",
    extraParams: "",
    remark: "",
    timeoutSecond: 30,
    inputPricePer1k: null,
    outputPricePer1k: null,
    currency: "CNY",
    usageScenarios: "",
    allowSummary: 1,
    allowReport: 1,
    allowFixedInfo: 1,
    allowDeepChat: 1,
  });
}

function validateModelForm() {
  if (!modelForm.apiUrl && !modelForm.baseUrl) {
    ElMessage.error("请填写 Base URL 或 API URL");
    return false;
  }
  if (!editingModelId.value && !modelForm.apiKey) {
    ElMessage.error("新增模型必须填写 API Key");
    return false;
  }
  if (modelForm.extraParams.trim()) {
    try {
      const extraParams = JSON.parse(modelForm.extraParams);
      if (!extraParams || Array.isArray(extraParams) || typeof extraParams !== "object") {
        throw new Error("高级 JSON 参数必须是对象");
      }
    } catch (error) {
      ElMessage.error(error instanceof Error ? error.message : "高级 JSON 参数不是合法 JSON");
      return false;
    }
  }
  return true;
}

async function saveSystemSettings() {
  await Promise.all(
    systemSettingDefinitions.map((setting) =>
      api.saveConfig({
        configKey: setting.key,
        configValue: serializeSystemSettingValue(systemSettingValues[setting.key]),
        description: setting.description,
      }),
    ),
  );
  ElMessage.success("系统配置已保存");
  configs.value = await api.configs();
  hydrateSystemSettings();
}

function hydrateSystemSettings() {
  const configMap = new Map(configs.value.map((item) => [item.configKey, item.configValue]));
  systemSettingDefinitions.forEach((setting) => {
    systemSettingValues[setting.key] = normalizeSystemSettingValue(setting, configMap.get(setting.key));
  });
}

function normalizeSystemSettingValue(setting: (typeof systemSettingDefinitions)[number], rawValue?: string) {
  if (rawValue === undefined || rawValue === "") {
    return setting.defaultValue;
  }
  if (setting.type === "switch") {
    return ["1", "true", "yes", "on", "启用"].includes(rawValue.toLowerCase());
  }
  if (setting.type === "text" || setting.type === "password") {
    return rawValue;
  }
  const parsed = Number(rawValue);
  if (Number.isFinite(parsed)) {
    return parsed;
  }
  return setting.defaultValue;
}

function serializeSystemSettingValue(value: SystemSettingValue) {
  if (typeof value === "boolean") return value ? "true" : "false";
  return String(value);
}

async function openRetrievalDetail(row: RetrievalLogItem) {
  selectedRetrievalLog.value = await api.retrievalLogDetail(row.id);
  retrievalDetailOpen.value = true;
}

async function updateAttentionStatus(row: AttentionItem, status: string) {
  await api.updateAttention(row.id, status);
  ElMessage.success("人工关注状态已更新");
  await loadAttention(pagers.attention.page, pagers.attention.pageSize);
}

async function deleteAttentionRecord(row: AttentionItem) {
  try {
    await ElMessageBox.confirm("确定删除这条人工关注记录吗？操作将写入审计日志。", "删除人工关注记录", {
      confirmButtonText: "确认删除", cancelButtonText: "取消", type: "warning",
    });
  } catch {
    return;
  }
  await api.deleteAttention(row.id);
  ElMessage.success("人工关注记录已删除");
  await loadAttention(pagers.attention.page, pagers.attention.pageSize);
}

function attentionPriorityLabel(priority: string) {
  return { urgent: "紧急", important: "重要", normal: "普通" }[priority as "urgent" | "important" | "normal"] || priority;
}

async function clearFeishuCache() {
  try {
    const result = await api.clearFeishuCache();
    ElMessage.success(result.message || `已清空 ${result.cleared} 条缓存`);
  } catch (e: any) {
    ElMessage.error(e.message || "刷新缓存失败");
  }
}

async function searchChats() {
  pagers.chats.page = 1;
  await loadRecordTab("chats");
}

async function resetChatFilters() {
  Object.assign(chatFilters, { keyword: "", userId: undefined, status: "", dateFrom: "", dateTo: "" });
  await searchChats();
}

async function searchQuestionInsights() {
  pagers.questionInsights.page = 1;
  loading.value = true;
  try {
    const refresh = await api.refreshQuestionInsights({
      dateFrom: formatRecordDateTime(questionInsightFilters.dateFrom, "start"),
      dateTo: formatRecordDateTime(questionInsightFilters.dateTo, "end"),
      maxMessages: questionInsightFilters.maxMessages,
    });
    await loadQuestionInsights(1, pagers.questionInsights.pageSize);
    if (refresh.processedMessages > 0) {
      ElMessage.success(
        `新增清洗 ${refresh.processedMessages} 条消息，得到 ${refresh.acceptedQuestions} 个有效问题${refresh.hasMore ? "；仍有历史消息待清洗" : ""}`,
      );
    } else {
      ElMessage.success("没有新增消息，已直接使用持久化清洗结果统计");
    }
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "问题洞察刷新失败");
  } finally {
    loading.value = false;
  }
}

async function resetQuestionInsightFilters() {
  Object.assign(questionInsightFilters, { dateFrom: "", dateTo: "", minCount: 2, maxMessages: 5000 });
  await searchQuestionInsights();
}

async function openChatDetail(row: ChatRecord) {
  await openChatSession(row.id);
}

async function openChatSession(sessionId: number) {
  chatDetailOpen.value = true;
  chatDetail.value = null;
  chatDetailSessionId.value = sessionId;
  Object.assign(chatDetailMessagePager, { page: 1, pageSize: chatDetailMessagePager.pageSize, total: 0 });
  await loadChatDetail(sessionId, 1, chatDetailMessagePager.pageSize);
}

async function loadChatDetail(sessionId = chatDetailSessionId.value, page = chatDetailMessagePager.page, pageSize = chatDetailMessagePager.pageSize) {
  if (!sessionId) return;
  chatDetailLoading.value = true;
  try {
    const detail = await api.chatDetail(sessionId, { messagePage: page, messagePageSize: pageSize });
    chatDetail.value = detail;
    const messagePage = detail.messagesPage;
    Object.assign(chatDetailMessagePager, {
      page: messagePage?.page ?? page,
      pageSize: messagePage?.pageSize ?? pageSize,
      total: messagePage?.total ?? detail.messages.length,
    });
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "聊天详情加载失败");
  } finally {
    chatDetailLoading.value = false;
  }
}

async function changeChatDetailMessagePage(page: number, pageSize: number) {
  if (!chatDetailSessionId.value || !chatDetail.value) return;
  chatMessagesLoading.value = true;
  try {
    const result = await api.chatMessages(chatDetailSessionId.value, { page, pageSize });
    chatDetail.value = {
      ...chatDetail.value,
      messages: result.items,
      messagesPage: result,
    };
    Object.assign(chatDetailMessagePager, { page: result.page, pageSize: result.pageSize, total: result.total });
    await nextTick();
    chatDetailBodyRef.value?.scrollTo({ top: 0, behavior: "smooth" });
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "聊天消息加载失败");
  } finally {
    chatMessagesLoading.value = false;
  }
}

async function openAiLogDetail(row: AiLogRecord) {
  selectedAiLog.value = await api.aiLogDetail(row.id);
  aiLogDetailOpen.value = true;
}

async function exportChats() {
  await api.exportChats(buildChatQuery());
}

function buildChatQuery() {
  return {
    keyword: chatFilters.keyword,
    userId: chatFilters.userId,
    status: chatFilters.status,
    dateFrom: formatRecordDateTime(chatFilters.dateFrom, "start"),
    dateTo: formatRecordDateTime(chatFilters.dateTo, "end"),
  };
}

function formatRecordDateTime(value: string, boundary: "start" | "end") {
  if (!value) return "";
  const [date, time = ""] = value.trim().replace("T", " ").split(" ");
  if (!date) return "";
  const [hour = "00", minute = "00", second] = time.split(":");
  const normalizedSecond = second ?? (boundary === "end" ? "59" : "00");
  return `${date} ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${normalizedSecond.padStart(2, "0")}`;
}
</script>

<template>
  <AdminLoginView v-if="!admin" :loading="loading" @submit="login" />

  <main v-else class="admin-shell">
    <aside class="sidebar">
      <div class="sidebar-title">大本营千问千答</div>
      <button :class="{ active: activeMenu === 'dashboard' }" @click="switchMenu('dashboard')">数据看板</button>
      <button :class="{ active: activeMenu === 'users' }" @click="switchMenu('users')">用户管理</button>
      <button :class="{ active: activeMenu === 'entitlements' }" @click="switchMenu('entitlements')">权益管理</button>
      <button :class="{ active: activeMenu === 'knowledge' }" @click="switchMenu('knowledge')">知识库管理</button>
      <button :class="{ active: activeMenu === 'prompt' }" @click="switchMenu('prompt')">Agent 管理</button>
      <button :class="{ active: activeMenu === 'models' }" @click="switchMenu('models')">模型管理</button>
      <button :class="{ active: activeMenu === 'configs' }" @click="switchMenu('configs')">系统配置</button>
      <button :class="{ active: activeMenu === 'records' }" @click="switchMenu('records')">记录审计</button>
      <button :class="{ active: activeMenu === 'retrievals' }" @click="switchMenu('retrievals')">检索日志</button>
      <button :class="{ active: activeMenu === 'attention' }" @click="switchMenu('attention')">人工关注</button>
    </aside>

    <section class="workspace">
      <header class="topbar">
        <div>
          <strong>{{ admin.name }}</strong>
          <span>管理员</span>
        </div>
        <el-button @click="logout">退出</el-button>
      </header>

      <section v-loading="loading" class="content">
        <template v-if="activeMenu === 'dashboard'">
          <div class="page-head inline">
            <div><h2>数据看板</h2><p>核心业务数据概览。</p></div>
            <div class="dashboard-date-range" aria-label="看板日期范围">
              <label class="record-date-field dashboard-date-field">
                <span>开始日期</span>
                <input v-model="dashboardFilters.start" type="date" aria-label="开始日期" @change="loadDashboard" />
              </label>
              <span class="record-date-sep">至</span>
              <label class="record-date-field dashboard-date-field">
                <span>结束日期</span>
                <input v-model="dashboardFilters.end" type="date" aria-label="结束日期" @change="loadDashboard" />
              </label>
            </div>
          </div>
          <div class="stats-grid">
            <div class="stat"><span>用户数</span><strong>{{ stats?.userCount ?? 0 }}</strong></div>
            <div class="stat"><span>会话数</span><strong>{{ stats?.sessionCount ?? 0 }}</strong></div>
            <div class="stat"><span>消息数</span><strong>{{ stats?.messageCount ?? 0 }}</strong></div>
            <div class="stat"><span>AI 请求</span><strong>{{ stats?.aiRequestCount ?? 0 }}</strong></div>
            <div class="stat"><span>知识库</span><strong>{{ stats?.knowledgeCount ?? 0 }}</strong></div>
            <div class="stat"><span>输入 Token</span><strong>{{ stats?.inputToken ?? 0 }}</strong></div>
            <div class="stat"><span>输出 Token</span><strong>{{ stats?.outputToken ?? 0 }}</strong></div>
            <div class="stat"><span>总 Token</span><strong>{{ stats?.totalToken ?? 0 }}</strong></div>
            <div class="stat"><span>估算成本</span><strong>{{ formatMoney(stats?.estimatedCost, stats?.costCurrency || 'CNY') }}</strong></div>
          </div>
          <section class="storage-panel" v-loading="storageLoading"><div class="storage-panel-head"><div><h3>项目存储占用</h3><p>数据库、Redis 与附件分别统计；无权限或不支持时显示“无法统计”。</p></div><el-button :loading="storageLoading" @click="refreshStorage">手动刷新</el-button></div><div class="storage-grid"><div><span>项目总量</span><strong>{{ formatBytes(storageStats?.totalBytes) }}</strong></div><div><span>数据库</span><strong>{{ formatBytes(storageStats?.detail?.database?.bytes) }}</strong></div><div><span>Redis</span><strong>{{ formatBytes(storageStats?.detail?.redis?.bytes) }}</strong></div><div><span>附件/文件</span><strong>{{ formatBytes(storageStats?.detail?.files?.bytes) }}</strong></div><div><span>近7天增长</span><strong>{{ formatBytes(storageStats?.growth7DaysBytes) }}</strong></div><div><span>近30天增长</span><strong>{{ formatBytes(storageStats?.growth30DaysBytes) }}</strong></div></div><small>最后统计：{{ storageStats?.createdAt || '尚未统计' }}</small></section>
        </template>

        <template v-if="activeMenu === 'users'">
          <div class="page-head inline">
            <div><h2>用户管理</h2><p>维护学员名单；只有名单内启用学员可以登录用户端。</p></div>
            <el-input v-model="userKeyword" placeholder="手机号或姓名" clearable @change="loadUsers(1)" />
          </div>
          <section class="student-tools">
            <div class="student-tool-panel">
              <h3>手动添加学员</h3>
              <div class="student-form-grid">
                <el-input v-model="studentForm.phone" placeholder="手机号" />
                <el-input v-model="studentForm.name" placeholder="姓名" />
                <el-input v-model="studentForm.nickname" placeholder="昵称（可选）" />
                <el-input-number v-model="studentForm.dailyChatLimit" :min="0" />
                <label class="record-date-field student-expiry-field">
                  <span>有效期</span>
                  <input v-model="studentForm.expiredAt" type="date" aria-label="学员有效期" title="留空表示长期有效" />
                </label>
                <div class="student-status-field">
                  <span>启用</span>
                  <el-switch
                    v-model="studentForm.status"
                    :active-value="1"
                    :inactive-value="0"
                    aria-label="启用学员账号"
                  />
                </div>
              </div>
              <div class="actions compact">
                <el-button type="primary" @click="createStudent">添加学员</el-button>
                <el-button @click="resetStudentForm">清空</el-button>
              </div>
            </div>
            <div class="student-tool-panel">
              <h3>批量导入学员</h3>
              <p class="tool-help">下载模板后填写学员信息，再上传 .xlsx 文件导入。</p>
              <div class="student-file-import">
                <input
                  ref="studentImportInput"
                  class="file-input"
                  type="file"
                  accept=".xlsx"
                  @change="handleStudentImportFile"
                />
                <span class="file-name">{{ studentImportFile?.name || "未选择文件" }}</span>
              </div>
              <div class="actions compact">
                <el-button @click="downloadStudentTemplate">下载模板</el-button>
                <el-button type="primary" @click="importStudents">导入 Excel</el-button>
                <span v-if="studentImportResult" class="import-result">
                  新增 {{ studentImportResult.created }}，更新 {{ studentImportResult.updated }}，失败 {{ studentImportResult.failed }}
                </span>
              </div>
              <div v-if="studentImportResult?.failures.length" class="import-failures">
                <span v-for="failure in studentImportResult.failures.slice(0, 5)" :key="`${failure.row}-${failure.phone}`">
                  第 {{ failure.row }} 行 {{ failure.phone || '-' }}：{{ failure.reason }}
                </span>
              </div>
            </div>
          </section>
          <el-table :data="users" stripe>
            <el-table-column prop="id" label="ID" width="80" />
            <el-table-column prop="phone" label="手机号" width="150" />
            <el-table-column prop="name" label="姓名" width="140" />
            <el-table-column prop="nickname" label="昵称" width="140" />
            <el-table-column label="状态" width="130">
              <template #default="{ row }"><el-switch v-model="row.status" :active-value="1" :inactive-value="0" /></template>
            </el-table-column>
            <el-table-column label="每日额度" width="150">
              <template #default="{ row }"><el-input-number v-model="row.dailyChatLimit" :min="0" size="small" /></template>
            </el-table-column>
            <el-table-column prop="dailyChatUsed" label="已用" width="90" />
            <el-table-column label="权益版本" min-width="260">
              <template #default="{ row }">
                <div class="user-entitlement-cell">
                  <el-select
                    :model-value="row.entitlement?.planId"
                    placeholder="默认基础版"
                    size="small"
                    @change="(planId: number) => assignUserEntitlement(row, planId)"
                  >
                    <el-option
                      v-for="plan in entitlementPlans.filter((item) => item.status === 1)"
                      :key="plan.id"
                      :label="plan.name"
                      :value="plan.id"
                    />
                  </el-select>
                  <small>
                    {{ row.entitlement?.name || '默认基础版' }}
                    · 本月主题
                    {{ row.entitlement?.monthlyTopicUsed ?? 0 }}/{{ row.entitlement?.monthlyTopicLimit ?? '不限' }}
                  </small>
                </div>
              </template>
            </el-table-column>
            <el-table-column prop="lastLoginAt" label="最近登录" width="180" />
            <el-table-column label="操作" width="210" fixed="right">
              <template #default="{ row }">
                <TableRowActions>
                  <el-button size="small" @click="openUserDetail(row)">详情</el-button>
                  <el-button size="small" type="primary" @click="saveUser(row)">保存</el-button>
                  <el-dropdown trigger="click">
                    <el-button size="small">更多</el-button>
                    <template #dropdown>
                      <el-dropdown-menu>
                        <el-dropdown-item @click="deleteUser(row)">删除用户</el-dropdown-item>
                      </el-dropdown-menu>
                    </template>
                  </el-dropdown>
                </TableRowActions>
              </template>
            </el-table-column>
          </el-table>
          <AdminPagination :page="pagers.users.page" :page-size="pagers.users.pageSize" :total="pagers.users.total" @change="loadUsers" />
        </template>

        <template v-if="activeMenu === 'entitlements'">
          <div class="page-head">
            <h2>权益管理</h2>
            <p>配置不同服务版本的本月主题额度和可用能力；学员可在用户管理里直接分配。</p>
          </div>

          <section class="entitlement-editor">
            <div class="entitlement-editor-head">
              <div>
                <h3>{{ editingEntitlementPlanId ? '编辑权益版本' : '新增权益版本' }}</h3>
                <p>一期先接入主题额度、成长档案/周期报告开关和转人工卡片/分享草稿能力位。</p>
              </div>
              <el-button @click="resetEntitlementPlanForm">清空</el-button>
            </div>
            <el-form label-position="top" :model="entitlementPlanForm">
              <div class="entitlement-form-grid">
                <el-form-item label="版本名称">
                  <el-input v-model="entitlementPlanForm.name" placeholder="例如：五个月深度陪伴版" />
                </el-form-item>
                <el-form-item label="版本类型">
                  <el-select v-model="entitlementPlanForm.planType">
                    <el-option label="基础版" value="basic" />
                    <el-option label="深度陪伴版" value="deep" />
                    <el-option label="高频加购包" value="addon" />
                    <el-option label="老师工作版" value="teacher" />
                  </el-select>
                </el-form-item>
                <el-form-item label="有效天数">
                  <el-input-number v-model="entitlementPlanForm.validityDays" :min="1" :max="3650" placeholder="留空长期有效" />
                </el-form-item>
                <el-form-item label="本月主题额度">
                  <el-input-number v-model="entitlementPlanForm.monthlyTopicLimit" :min="0" :max="99999" placeholder="留空不限" />
                </el-form-item>
                <el-form-item label="排序">
                  <el-input-number v-model="entitlementPlanForm.sortOrder" :min="0" :max="9999" />
                </el-form-item>
              </div>
              <el-form-item label="说明">
                <el-input v-model="entitlementPlanForm.description" type="textarea" :rows="2" placeholder="给后台管理员看的说明，不展示给学员" />
              </el-form-item>
              <div class="entitlement-switch-grid">
                <label><span>成长档案</span><el-switch v-model="entitlementPlanForm.enableGrowthProfile" :active-value="1" :inactive-value="0" /></label>
                <label><span>周期报告</span><el-switch v-model="entitlementPlanForm.enablePeriodicReports" :active-value="1" :inactive-value="0" /></label>
                <label><span>允许求助卡片</span><el-switch v-model="entitlementPlanForm.allowHelpCard" :active-value="1" :inactive-value="0" /></label>
                <label><span>允许分享草稿</span><el-switch v-model="entitlementPlanForm.allowShareDraft" :active-value="1" :inactive-value="0" /></label>
                <label><span>占用主题额度</span><el-switch v-model="entitlementPlanForm.deductQuota" :active-value="1" :inactive-value="0" /></label>
                <label><span>启用版本</span><el-switch v-model="entitlementPlanForm.status" :active-value="1" :inactive-value="0" /></label>
              </div>
              <div class="actions">
                <el-button type="primary" @click="saveEntitlementPlan">{{ editingEntitlementPlanId ? '保存权益版本' : '新增权益版本' }}</el-button>
                <el-button @click="resetEntitlementPlanForm">取消编辑</el-button>
              </div>
            </el-form>
          </section>

          <el-table :data="entitlementPlans" stripe>
            <el-table-column prop="name" label="版本名称" min-width="180" />
            <el-table-column label="类型" width="130">
              <template #default="{ row }">{{ planTypeLabel(row.planType) }}</template>
            </el-table-column>
            <el-table-column label="本月主题额度" width="130">
              <template #default="{ row }">{{ row.monthlyTopicLimit ?? '不限' }}</template>
            </el-table-column>
            <el-table-column label="有效期" width="110">
              <template #default="{ row }">{{ row.validityDays ? `${row.validityDays} 天` : '长期' }}</template>
            </el-table-column>
            <el-table-column label="能力" min-width="260">
              <template #default="{ row }">
                <div class="entitlement-capabilities">
                  <el-tag v-if="row.enableGrowthProfile" type="success" effect="plain">成长档案</el-tag>
                  <el-tag v-if="row.enablePeriodicReports" type="success" effect="plain">周期报告</el-tag>
                  <el-tag v-if="row.allowHelpCard" effect="plain">求助卡片</el-tag>
                  <el-tag v-if="row.allowShareDraft" effect="plain">分享草稿</el-tag>
                  <el-tag :type="row.deductQuota ? 'warning' : 'info'" effect="plain">{{ row.deductQuota ? '计入额度' : '不计额度' }}</el-tag>
                </div>
              </template>
            </el-table-column>
            <el-table-column label="状态" width="90">
              <template #default="{ row }"><el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '启用' : '停用' }}</el-tag></template>
            </el-table-column>
            <el-table-column label="操作" width="100" fixed="right" align="center">
              <template #default="{ row }"><el-button size="small" @click="editEntitlementPlan(row)">编辑</el-button></template>
            </el-table-column>
          </el-table>
        </template>

        <template v-if="activeMenu === 'knowledge'">
          <KnowledgeManagementView @preview="previewKnowledge" />
        </template>

        <template v-if="activeMenu === 'prompt'">
          <AgentManagementView :preview-knowledge-id="previewKnowledgeId" @consumed-preview="previewKnowledgeId = null" />
        </template>

        <template v-if="activeMenu === 'models'">
          <div class="page-head inline">
            <div><h2>模型管理</h2><p>DeepSeek、MiniMax 可快速添加；其他供应商按 OpenAI 兼容或 Anthropic Messages 协议添加。</p></div>
            <el-button @click="resetModelForm">清空表单</el-button>
          </div>

          <section class="quick-model-panel">
            <div class="quick-model-head">
              <div>
                <h3>快速添加</h3>
                <p>适用于 DeepSeek 和 MiniMax，只需要填写 API Key 并选择模型。</p>
              </div>
            </div>
            <div class="quick-model-grid">
              <el-form-item label="厂商">
                <el-select v-model="quickModelForm.provider" @change="applyQuickProvider">
                  <el-option
                    v-for="provider in quickModelProviders"
                    :key="provider.provider"
                    :label="provider.label"
                    :value="provider.provider"
                  />
                </el-select>
              </el-form-item>
              <el-form-item label="模型">
                <el-select v-model="quickModelForm.modelName" :key="quickModelForm.provider">
                  <el-option
                    v-for="model in selectedQuickProvider.models"
                    :key="model.value"
                    :label="model.label"
                    :value="model.value"
                  />
                </el-select>
              </el-form-item>
              <el-form-item label="API Key">
                <el-input v-model="quickModelForm.apiKey" placeholder="填写后保存，列表中只展示掩码" show-password />
              </el-form-item>
              <el-form-item label=" ">
                <el-button type="primary" class="full-btn" @click="quickAddModel">快速新增</el-button>
              </el-form-item>
            </div>
          </section>

          <el-form class="model-config-panel" label-position="top" :model="modelForm">
            <div class="model-config-grid">
              <el-form-item label="协议模板">
                <el-select placeholder="选择协议模板" clearable @change="applyModelPreset">
                  <el-option v-for="preset in protocolPresets" :key="preset.label" :label="preset.label" :value="preset.label" />
                </el-select>
              </el-form-item>
              <el-form-item label="供应商标识">
                <el-input v-model="modelForm.provider" placeholder="custom-openai / custom-anthropic" />
              </el-form-item>
              <el-form-item label="显示名称">
                <el-input v-model="modelForm.displayName" placeholder="例如：自建网关生产模型" />
              </el-form-item>
              <el-form-item label="协议类型">
                <el-select v-model="modelForm.apiType">
                  <el-option label="OpenAI 兼容" value="openai_compatible" />
                  <el-option label="Anthropic Messages" value="anthropic_messages" />
                  <el-option label="MiniMax 官方" value="minimax" />
                </el-select>
              </el-form-item>
              <el-form-item label="模型名">
                <el-input v-model="modelForm.modelName" placeholder="gpt-4.1 / deepseek-chat / claude..." />
              </el-form-item>
              <el-form-item label="Base URL">
                <el-input v-model="modelForm.baseUrl" placeholder="https://api.example.com/v1" />
              </el-form-item>
              <el-form-item label="API URL（可覆盖）">
                <el-input v-model="modelForm.apiUrl" placeholder="留空则按协议自动拼接" />
              </el-form-item>
              <el-form-item label="鉴权方式">
                <el-select v-model="modelForm.authType">
                  <el-option label="Bearer Token" value="bearer" />
                  <el-option label="x-api-key" value="api_key" />
                </el-select>
              </el-form-item>
              <el-form-item label="API Key">
                <el-input v-model="modelForm.apiKey" placeholder="保存时会覆盖旧 Key" show-password />
              </el-form-item>
              <el-form-item label="API Version">
                <el-input v-model="modelForm.apiVersion" placeholder="Anthropic 默认 2023-06-01" />
              </el-form-item>
              <el-form-item label="超时秒数">
                <el-input-number v-model="modelForm.timeoutSecond" :min="1" :max="300" />
              </el-form-item>
              <el-form-item label="输入单价/千Token">
                <el-input-number v-model="modelForm.inputPricePer1k" :min="0" :precision="6" :step="0.001" placeholder="不填则不估算成本" />
              </el-form-item>
              <el-form-item label="输出单价/千Token">
                <el-input-number v-model="modelForm.outputPricePer1k" :min="0" :precision="6" :step="0.001" placeholder="不填则不估算成本" />
              </el-form-item>
              <el-form-item label="币种">
                <el-input v-model="modelForm.currency" placeholder="CNY / USD" maxlength="10" />
              </el-form-item>
            </div>
            <el-form-item label="适用场景">
              <el-input v-model="modelForm.usageScenarios" placeholder="例如：正式对话、摘要、固定信息、报告批处理" />
            </el-form-item>
            <el-form-item label="可用能力">
              <div class="entitlement-capabilities">
                <el-checkbox v-model="modelForm.allowFixedInfo" :true-value="1" :false-value="0">固定信息</el-checkbox>
                <el-checkbox v-model="modelForm.allowDeepChat" :true-value="1" :false-value="0">深度对话</el-checkbox>
                <el-checkbox v-model="modelForm.allowSummary" :true-value="1" :false-value="0">摘要沉淀</el-checkbox>
                <el-checkbox v-model="modelForm.allowReport" :true-value="1" :false-value="0">周期报告</el-checkbox>
              </div>
            </el-form-item>
            <el-form-item label="备注">
              <el-input v-model="modelForm.remark" placeholder="用途、额度、注意事项等" />
            </el-form-item>
            <div class="actions">
              <el-button type="primary" @click="saveModel">{{ editingModelId ? "保存模型" : "新增模型" }}</el-button>
              <el-button @click="resetModelForm">取消编辑</el-button>
            </div>
          </el-form>
          <el-table :data="models" stripe>
            <el-table-column prop="displayName" label="显示名称" width="170" />
            <el-table-column prop="provider" label="Provider" />
            <el-table-column prop="apiType" label="协议" width="170" />
            <el-table-column prop="modelName" label="模型" />
            <el-table-column label="估算单价" width="180">
              <template #default="{ row }">{{ row.currency || 'CNY' }} {{ row.inputPricePer1k ?? '-' }}/{{ row.outputPricePer1k ?? '-' }}</template>
            </el-table-column>
            <el-table-column prop="usageScenarios" label="适用场景" min-width="160" show-overflow-tooltip />
            <el-table-column prop="baseUrl" label="Base URL" min-width="220" show-overflow-tooltip />
            <el-table-column prop="authType" label="鉴权" width="110" />
            <el-table-column prop="enabled" label="启用" width="90" />
            <el-table-column label="操作" width="190" fixed="right" align="center">
              <template #default="{ row }">
                <TableRowActions><el-button size="small" @click="editModel(row)">编辑</el-button><el-button size="small" @click="testModel(row.id)">测试</el-button><el-dropdown trigger="click" teleported @command="(command: string) => command === 'enable' ? enableModel(row.id) : deleteModel(row.id)"><el-button size="small">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="enable">启用</el-dropdown-item><el-dropdown-item command="delete" divided>删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></TableRowActions>
              </template>
            </el-table-column>
          </el-table>
        </template>

        <template v-if="activeMenu === 'configs'">
          <div class="page-head inline">
            <div><h2>系统配置</h2><p>按业务分组维护运行时配置，默认每日额度已接入学员新增和导入。</p></div>
            <el-button type="primary" @click="saveSystemSettings">保存系统配置</el-button>
          </div>
          <section class="settings-grid">
            <div v-for="section in systemSettingSections" :key="section.title" class="settings-section">
              <div class="settings-section-head">
                <h3>{{ section.title }}</h3>
                <p>{{ section.description }}</p>
              </div>
              <div class="settings-fields">
                <label v-for="setting in section.settings" :key="setting.key" class="setting-field">
                  <span>{{ setting.label }}</span>
                  <el-input-number
                    v-if="setting.type === 'number'"
                    v-model="systemSettingValues[setting.key]"
                    :min="setting.min"
                    :max="setting.max"
                  />
                  <el-input
                    v-else-if="setting.type === 'text'"
                    v-model="systemSettingValues[setting.key]"
                    :placeholder="setting.placeholder"
                  />
                  <el-input
                    v-else-if="setting.type === 'password'"
                    v-model="systemSettingValues[setting.key]"
                    :placeholder="setting.placeholder"
                    type="password"
                    show-password
                  />
                  <el-switch v-else v-model="systemSettingValues[setting.key]" />
                  <small>{{ setting.description }}</small>
                </label>
              </div>
            </div>
          </section>
        </template>

        <template v-if="activeMenu === 'retrievals'">
          <div class="page-head inline"><div><h2>检索日志</h2><p>查看每条回答使用的知识目录、Agent 决策、搜索候选、重排和章节回读。</p></div><div class="cleanup-controls"><el-date-picker v-model="cleanupBefore" value-format="YYYY-MM-DD" type="date" placeholder="清理此日期前" /><el-button type="danger" plain @click="cleanupRetrievalLogs">清理日志</el-button></div></div>
          <section class="retention-panel"><div><strong>定期清理</strong><span>每日自动分批删除过期检索日志和关联候选明细</span></div><el-select v-model="retentionDays" placeholder="保留时间"><el-option label="永久保留" :value="null" /><el-option label="30天" :value="30" /><el-option label="90天" :value="90" /><el-option label="180天" :value="180" /></el-select><el-button type="primary" @click="saveRetention">保存规则</el-button><small>最近执行：{{ retentionState?.lastRunAt || '尚未执行' }}</small></section>
          <el-table :data="retrievalLogs" stripe>
            <el-table-column prop="id" label="ID" width="80" />
            <el-table-column prop="sessionId" label="会话" width="90" />
            <el-table-column prop="question" label="用户问题" min-width="280" show-overflow-tooltip />
            <el-table-column label="调用知识" width="100"><template #default="{ row }"><el-tag :type="row.knowledgeCalled ? 'success' : 'info'">{{ row.knowledgeCalled ? '已调用' : '未调用' }}</el-tag></template></el-table-column>
            <el-table-column prop="selectedKnowledgeIds" label="知识库" width="120" />
            <el-table-column prop="status" label="状态" width="100" />
            <el-table-column prop="toolCostMs" label="工具耗时" width="110" />
            <el-table-column prop="totalCostMs" label="总耗时" width="100" />
            <el-table-column prop="createdAt" label="时间" width="180" />
            <el-table-column label="操作" width="90" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openRetrievalDetail(row)">详情</el-button></template></el-table-column>
          </el-table>
          <AdminPagination :page="pagers.retrievals.page" :page-size="pagers.retrievals.pageSize" :total="pagers.retrievals.total" @change="loadRetrievals" />
        </template>

        <template v-if="activeMenu === 'attention'">
          <div class="page-head"><h2>人工关注</h2><p>处理安全风险、复杂卡住、知识缺失和用户主动求助记录。</p></div>
          <el-table :data="attentionRecords" stripe>
            <el-table-column label="优先级" width="100"><template #default="{ row }"><el-tag :type="row.priority === 'urgent' ? 'danger' : row.priority === 'important' ? 'warning' : 'info'">{{ attentionPriorityLabel(row.priority) }}</el-tag></template></el-table-column>
            <el-table-column prop="problemSummary" label="问题摘要" min-width="220" />
            <el-table-column prop="triggerReason" label="触发原因" min-width="220" />
            <el-table-column prop="userId" label="用户" width="80" />
            <el-table-column prop="sessionId" label="会话" width="80" />
            <el-table-column label="状态" width="150"><template #default="{ row }"><el-select :model-value="row.status" size="small" @change="updateAttentionStatus(row, $event)"><el-option label="待处理" value="pending" /><el-option label="处理中" value="processing" /><el-option label="已处理" value="resolved" /><el-option label="无需处理" value="ignored" /></el-select></template></el-table-column>
            <el-table-column prop="createdAt" label="创建时间" width="180" />
            <el-table-column label="操作" width="130" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openChatSession(row.sessionId)">原会话</el-button><el-button link type="danger" @click="deleteAttentionRecord(row)">删除</el-button></template></el-table-column>
          </el-table>
          <AdminPagination :page="pagers.attention.page" :page-size="pagers.attention.pageSize" :total="pagers.attention.total" @change="loadAttention" />
        </template>

        <template v-if="activeMenu === 'records'">
          <div class="page-head"><h2>记录审计</h2><p>聊天、AI 请求和后台操作记录。</p></div>
          <el-tabs v-model="recordTab">
            <el-tab-pane label="聊天记录" name="chats">
              <div class="record-filter">
                <el-input v-model="chatFilters.keyword" placeholder="搜索标题、手机号、姓名、聊天内容" clearable />
                <el-input-number v-model="chatFilters.userId" :min="1" placeholder="用户ID" />
                <el-select v-model="chatFilters.status" placeholder="消息状态" clearable>
                  <el-option label="全部状态" value="" />
                  <el-option label="已完成" value="FINISHED" />
                  <el-option label="生成中" value="GENERATING" />
                  <el-option label="已停止" value="STOPPED" />
                  <el-option label="失败" value="FAILED" />
                </el-select>
                <div class="record-date-range" aria-label="时间范围">
                  <label class="record-date-field">
                    <span>开始时间</span>
                    <input v-model="chatFilters.dateFrom" type="datetime-local" aria-label="开始时间" />
                  </label>
                  <span class="record-date-sep">至</span>
                  <label class="record-date-field">
                    <span>结束时间</span>
                    <input v-model="chatFilters.dateTo" type="datetime-local" aria-label="结束时间" />
                  </label>
                </div>
                <div class="record-filter-actions">
                  <el-button type="primary" @click="searchChats">搜索</el-button>
                  <el-button @click="resetChatFilters">重置</el-button>
                  <el-button @click="exportChats">导出</el-button>
                </div>
              </div>
              <el-table v-loading="loading" :data="chats" stripe>
                <el-table-column prop="id" label="会话ID" width="90" />
                <el-table-column prop="userPhone" label="手机号" width="140" />
                <el-table-column prop="userName" label="用户" width="120" />
                <el-table-column prop="title" label="会话标题" min-width="220" show-overflow-tooltip />
                <el-table-column prop="messageCount" label="消息数" width="90" />
                <el-table-column prop="lastMessageAt" label="最后消息" width="180" />
                <el-table-column label="操作" width="100" fixed="right">
                  <template #default="{ row }">
                    <el-button size="small" @click="openChatDetail(row)">详情</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <AdminPagination :page="pagers.chats.page" :page-size="pagers.chats.pageSize" :total="pagers.chats.total" @change="(page, pageSize) => changeRecordPage('chats', page, pageSize)" />
            </el-tab-pane>
            <el-tab-pane label="问题洞察" name="questionInsights">
              <div class="record-filter question-insight-filter">
                <div class="record-date-range" aria-label="问题时间范围">
                  <label class="record-date-field">
                    <span>开始时间</span>
                    <input v-model="questionInsightFilters.dateFrom" type="datetime-local" aria-label="问题开始时间" />
                  </label>
                  <span class="record-date-sep">至</span>
                  <label class="record-date-field">
                    <span>结束时间</span>
                    <input v-model="questionInsightFilters.dateTo" type="datetime-local" aria-label="问题结束时间" />
                  </label>
                </div>
                <label class="insight-number-field">
                  <span>最低频次</span>
                  <el-input-number v-model="questionInsightFilters.minCount" :min="1" :max="50" controls-position="right" />
                </label>
                <label class="insight-number-field">
                  <span>单次清洗上限</span>
                  <el-input-number v-model="questionInsightFilters.maxMessages" :min="100" :max="20000" :step="500" controls-position="right" />
                </label>
                <div class="record-filter-actions">
                  <el-button type="primary" :loading="loading" @click="searchQuestionInsights">刷新并统计</el-button>
                  <el-button @click="resetQuestionInsightFilters">重置</el-button>
                </div>
              </div>
              <p class="question-insight-help">清洗结果会持久化保存；点击“刷新并统计”只处理尚未清洗的用户消息，再按当前时间范围聚合，翻页不会重新扫描聊天原文。</p>
              <section v-if="questionInsights" class="question-insight-summary">
                <div><span>纳入清洗消息</span><strong>{{ questionInsights.summary.scannedMessages }}</strong></div>
                <div><span>有效问题</span><strong>{{ questionInsights.summary.cleanedQuestions }}</strong></div>
                <div><span>过滤低价值</span><strong>{{ questionInsights.summary.filteredMessages }}</strong></div>
                <div><span>高频问题组</span><strong>{{ questionInsights.summary.visibleClusterCount }}</strong></div>
              </section>
              <section v-loading="loading" class="question-insight-list">
                <article v-for="cluster in questionInsights?.items || []" :key="`${cluster.rank}-${cluster.normalized}`" class="question-insight-card">
                  <header>
                    <div>
                      <small>TOP {{ cluster.rank }}</small>
                      <h3>{{ cluster.title }}</h3>
                    </div>
                    <div class="question-insight-metrics">
                      <span>{{ cluster.count }} 次</span>
                      <span>{{ cluster.userCount }} 人</span>
                      <span>{{ cluster.sessionCount }} 个会话</span>
                    </div>
                  </header>
                  <div class="question-insight-tags">
                    <el-tag size="small" effect="plain">{{ cluster.categoryLabel }}</el-tag>
                    <el-tag v-if="cluster.needsKnowledgeFollowUp" size="small" type="warning" effect="plain">需补知识/查召回</el-tag>
                    <el-tag v-if="cluster.noHitCount" size="small" type="danger" effect="plain">无命中 {{ cluster.noHitCount }}</el-tag>
                    <el-tag v-if="cluster.failedCount" size="small" type="danger" effect="dark">失败 {{ cluster.failedCount }}</el-tag>
                    <el-tag v-for="term in cluster.topTerms" :key="term" size="small" type="success" effect="plain">{{ term }}</el-tag>
                  </div>
                  <p class="question-insight-action">{{ cluster.suggestedAction }}</p>
                  <el-collapse>
                    <el-collapse-item title="查看相似问法和原始样例" :name="cluster.normalized">
                      <div class="question-variants">
                        <span v-for="variant in cluster.variants" :key="variant.text">{{ variant.text }} × {{ variant.count }}</span>
                      </div>
                      <div class="question-samples">
                        <article v-for="sample in cluster.samples" :key="sample.messageId">
                          <div>
                            <strong>{{ sample.userName || sample.userPhone || `用户 #${sample.userId}` }}</strong>
                            <span>{{ sample.createdAt }}</span>
                          </div>
                          <p>清洗后：{{ sample.cleaned }}</p>
                          <pre>{{ sample.raw }}</pre>
                          <el-button link type="primary" @click="openChatSession(sample.sessionId)">查看原会话</el-button>
                        </article>
                      </div>
                    </el-collapse-item>
                  </el-collapse>
                </article>
                <el-empty v-if="questionInsights && questionInsights.items.length === 0" description="当前条件下暂无达到频次的问题组" :image-size="72" />
                <el-empty v-else-if="!questionInsights && !loading" description="选择时间范围后点击统计问题" :image-size="72" />
              </section>
              <AdminPagination :page="pagers.questionInsights.page" :page-size="pagers.questionInsights.pageSize" :total="pagers.questionInsights.total" @change="(page, pageSize) => changeRecordPage('questionInsights', page, pageSize)" />
            </el-tab-pane>
            <el-tab-pane label="AI 请求" name="aiLogs">
              <el-table v-loading="loading" :data="aiLogs" stripe>
                <el-table-column prop="sessionId" label="会话ID" width="90" />
                <el-table-column prop="modelName" label="模型" width="160" />
                <el-table-column prop="knowledgeIds" label="知识库" width="120" />
                <el-table-column prop="retrieveCount" label="命中" width="80" />
                <el-table-column label="片段" width="80">
                  <template #default="{ row }">
                    {{ row.retrievedChunks?.length || 0 }}
                  </template>
                </el-table-column>
                <el-table-column prop="totalToken" label="Token" width="90" />
                <el-table-column label="估算成本" width="130">
                  <template #default="{ row }">{{ formatMoney(row.estimatedCost, row.currency || 'CNY') }}</template>
                </el-table-column>
                <el-table-column prop="costMs" label="耗时(ms)" width="100" />
                <el-table-column prop="status" label="状态" width="100" />
                <el-table-column prop="errorMessage" label="错误" min-width="220" show-overflow-tooltip />
                <el-table-column label="操作" width="100" fixed="right">
                  <template #default="{ row }">
                    <el-button size="small" @click="openAiLogDetail(row)">详情</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <AdminPagination :page="pagers.aiLogs.page" :page-size="pagers.aiLogs.pageSize" :total="pagers.aiLogs.total" @change="(page, pageSize) => changeRecordPage('aiLogs', page, pageSize)" />
            </el-tab-pane>
            <el-tab-pane label="操作日志" name="operationLogs">
              <el-table v-loading="loading" :data="operationLogs" stripe>
                <el-table-column prop="module" label="模块" />
                <el-table-column prop="action" label="动作" />
                <el-table-column prop="result" label="结果" />
                <el-table-column prop="createdAt" label="时间" />
              </el-table>
              <AdminPagination :page="pagers.operationLogs.page" :page-size="pagers.operationLogs.pageSize" :total="pagers.operationLogs.total" @change="(page, pageSize) => changeRecordPage('operationLogs', page, pageSize)" />
            </el-tab-pane>
          </el-tabs>
        </template>
      </section>
    </section>

    <el-drawer v-model="userDetailOpen" size="860px" title="用户详情">
      <div v-loading="userDetailLoading" class="user-detail-body">
        <template v-if="selectedUserDetail">
          <section class="user-detail-head">
            <div>
              <h3>{{ selectedUserDetail.user.name || selectedUserDetail.user.nickname || selectedUserDetail.user.phone }}</h3>
              <p>{{ selectedUserDetail.user.phone }} · {{ selectedUserDetail.user.status === 1 ? '已启用' : '已停用' }}</p>
            </div>
            <div class="user-detail-tags">
              <el-tag v-if="selectedUserDetail.metrics.isHighFrequency" type="warning" effect="plain">高频使用</el-tag>
              <el-tag v-if="selectedUserDetail.metrics.isInactive" type="info" effect="plain">近30天低活跃</el-tag>
              <el-tag type="success" effect="plain">{{ selectedUserDetail.user.entitlement?.name || '默认基础版' }}</el-tag>
            </div>
          </section>

          <section class="chat-summary user-metric-grid">
            <div><span>本月主题</span><strong>{{ selectedUserDetail.metrics.monthTopics }}/{{ selectedUserDetail.user.entitlement?.monthlyTopicLimit ?? '不限' }}</strong></div>
            <div><span>近30天活跃</span><strong>{{ selectedUserDetail.metrics.recentActiveDays }} 天</strong></div>
            <div><span>总会话</span><strong>{{ selectedUserDetail.metrics.totalSessions }}</strong></div>
            <div><span>总消息</span><strong>{{ selectedUserDetail.metrics.totalMessages }}</strong></div>
            <div><span>主题总数</span><strong>{{ selectedUserDetail.metrics.totalTopics }}</strong></div>
            <div><span>AI 请求</span><strong>{{ selectedUserDetail.metrics.aiRequestCount }}</strong></div>
            <div><span>总 Token</span><strong>{{ selectedUserDetail.metrics.totalToken }}</strong></div>
            <div><span>估算成本</span><strong>{{ formatMoney(selectedUserDetail.metrics.estimatedCost, selectedUserDetail.metrics.costCurrency) }}</strong></div>
          </section>

          <section class="audit-detail-grid">
            <div><span>权益有效期</span><strong>{{ selectedUserDetail.user.entitlement?.expiredAt || selectedUserDetail.user.expiredAt || '长期有效' }}</strong></div>
            <div><span>最近登录</span><strong>{{ selectedUserDetail.user.lastLoginAt || '-' }}</strong></div>
            <div><span>最近发言</span><strong>{{ selectedUserDetail.metrics.lastMessageAt || '-' }}</strong></div>
            <div><span>生成记录</span><strong>求助卡 {{ selectedUserDetail.metrics.helpCardCount }} / 分享稿 {{ selectedUserDetail.metrics.shareDraftCount }}</strong></div>
          </section>

          <h3 class="detail-title">成长档案摘要</h3>
          <section v-if="selectedUserDetail.growthProfile" class="topic-summary-card">
            <pre>{{ selectedUserDetail.growthProfile.profileText || '暂无成长档案摘要' }}</pre>
            <div class="topic-summary-fields">
              <p v-if="selectedUserDetail.growthProfile.recurringTopics"><strong>反复议题</strong>{{ selectedUserDetail.growthProfile.recurringTopics }}</p>
              <p v-if="selectedUserDetail.growthProfile.commonEmotions"><strong>常见情绪</strong>{{ selectedUserDetail.growthProfile.commonEmotions }}</p>
              <p v-if="selectedUserDetail.growthProfile.bodyPatterns"><strong>身体模式</strong>{{ selectedUserDetail.growthProfile.bodyPatterns }}</p>
              <p v-if="selectedUserDetail.growthProfile.homeworkDone"><strong>做过功课</strong>{{ selectedUserDetail.growthProfile.homeworkDone }}</p>
              <p v-if="selectedUserDetail.growthProfile.effectiveHomework"><strong>有效功课</strong>{{ selectedUserDetail.growthProfile.effectiveHomework }}</p>
              <p v-if="selectedUserDetail.growthProfile.recentProgress"><strong>近期变化</strong>{{ selectedUserDetail.growthProfile.recentProgress }}</p>
            </div>
          </section>
          <el-empty v-else description="该用户暂无成长档案沉淀" :image-size="64" />

          <section class="user-report-head">
            <div>
              <h3 class="detail-title">周期报告</h3>
              <p>基于已沉淀的主题摘要生成，不读取整段原始聊天；同一周期重复生成会覆盖旧报告。</p>
            </div>
            <div class="user-report-actions">
              <el-button size="small" :loading="userReportGenerating === 'weekly'" @click="generateUserReport('weekly')">生成周报</el-button>
              <el-button size="small" :loading="userReportGenerating === 'monthly'" @click="generateUserReport('monthly')">生成月报</el-button>
              <el-button size="small" :loading="userReportGenerating === 'stage'" @click="generateUserReport('stage')">生成阶段总结</el-button>
            </div>
          </section>
          <el-collapse v-if="selectedUserDetail.recentReports.length" class="topic-summary-collapse">
            <el-collapse-item
              v-for="report in selectedUserDetail.recentReports"
              :key="report.id"
              :title="`#${report.id} ${report.title} / ${report.status}`"
            >
              <section class="topic-summary-card">
                <div class="topic-summary-meta">
                  <span>类型：{{ report.reportTypeLabel }}</span>
                  <span>周期：{{ report.periodStart }} 至 {{ report.periodEnd }}</span>
                  <span>来源主题：{{ report.sourceTopicIds.length }}</span>
                  <span>来源摘要：{{ report.sourceSummaryIds.length }}</span>
                  <span>模型：{{ report.modelName || '-' }}</span>
                  <span>生成：{{ report.generatedAt }}</span>
                </div>
                <p v-if="report.errorMessage" class="report-error">生成失败：{{ report.errorMessage }}</p>
                <pre>{{ report.content }}</pre>
              </section>
            </el-collapse-item>
          </el-collapse>
          <el-empty v-else description="该用户暂无周期报告" :image-size="64" />

          <h3 class="detail-title">最近主题</h3>
          <el-collapse v-if="selectedUserDetail.recentTopics.length" class="topic-summary-collapse">
            <el-collapse-item
              v-for="topic in selectedUserDetail.recentTopics"
              :key="topic.id"
              :title="`#${topic.id} ${topic.title} / ${topic.status}`"
            >
              <section class="topic-summary-card">
                <div class="topic-summary-meta">
                  <span>核心问题：{{ topic.coreQuestion }}</span>
                  <span>消息数：{{ topic.messageCount }}</span>
                  <span>Token：{{ topic.tokenInput }}/{{ topic.tokenOutput }}</span>
                  <span>额度：{{ topic.quotaDeducted ? '已计入' : '不计入' }}</span>
                  <span>开始：{{ topic.startedAt }}</span>
                  <span>更新：{{ topic.updatedAt }}</span>
                </div>
                <template v-if="topic.summary">
                  <pre>{{ topic.summary.summary }}</pre>
                  <div 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>
                </template>
                <el-empty v-else description="该主题尚未沉淀摘要" :image-size="60" />
              </section>
            </el-collapse-item>
          </el-collapse>
          <el-empty v-else description="该用户暂无主题记录" :image-size="64" />

          <h3 class="detail-title">最近老师求助卡（{{ selectedUserDetail.recentHelpCards.length }}）</h3>
          <el-collapse v-if="selectedUserDetail.recentHelpCards.length" class="topic-summary-collapse">
            <el-collapse-item
              v-for="card in selectedUserDetail.recentHelpCards"
              :key="card.id"
              :title="`#${card.id} / 主题 #${card.topicSessionId} / ${card.copied ? '用户已复制' : '未复制'}`"
            >
              <section class="topic-summary-card">
                <div class="topic-summary-meta">
                  <span>生成：{{ card.createdAt }}</span>
                  <span>复制：{{ card.copiedAt || '-' }}</span>
                  <span>来源：{{ card.source }}</span>
                </div>
                <pre>{{ card.content }}</pre>
              </section>
            </el-collapse-item>
          </el-collapse>
          <el-empty v-else description="该用户暂无求助卡生成记录" :image-size="64" />

          <h3 class="detail-title">最近班级分享稿（{{ selectedUserDetail.recentShareDrafts.length }}）</h3>
          <el-collapse v-if="selectedUserDetail.recentShareDrafts.length" class="topic-summary-collapse">
            <el-collapse-item
              v-for="draft in selectedUserDetail.recentShareDrafts"
              :key="draft.id"
              :title="`#${draft.id} / 主题 #${draft.topicSessionId} / ${draft.copied ? '用户已复制' : '未复制'}`"
            >
              <section class="topic-summary-card">
                <div class="topic-summary-meta">
                  <span>生成：{{ draft.createdAt }}</span>
                  <span>复制：{{ draft.copiedAt || '-' }}</span>
                  <span>来源：{{ draft.source }}</span>
                </div>
                <pre>{{ draft.content }}</pre>
              </section>
            </el-collapse-item>
          </el-collapse>
          <el-empty v-else description="该用户暂无分享稿生成记录" :image-size="64" />
        </template>
      </div>
    </el-drawer>

    <el-drawer v-model="retrievalDetailOpen" size="820px" title="检索日志详情">
      <template v-if="selectedRetrievalLog">
        <dl class="knowledge-meta">
          <dt>问题</dt><dd>{{ selectedRetrievalLog.question }}</dd>
          <dt>安全规则</dt><dd>{{ selectedRetrievalLog.safetyRuleVersion }}</dd>
          <dt>目录哈希</dt><dd>{{ selectedRetrievalLog.catalogHash }}</dd>
          <dt>最终章节</dt><dd>{{ selectedRetrievalLog.finalSectionIds || '未读取知识章节' }}</dd>
        </dl>
        <h3>本轮 KnowledgeList</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.catalogSnapshot, null, 2) }}</pre>
        <h3>Agent 工具轨迹</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.toolTrace, null, 2) }}</pre>
        <h3>候选切片与取舍</h3><el-table :data="selectedRetrievalLog.candidates"><el-table-column prop="knowledgeId" label="知识库" width="90" /><el-table-column prop="knowledgeName" label="名称" min-width="150" show-overflow-tooltip /><el-table-column prop="knowledgeTypeLabel" label="类型优先级" width="170" /><el-table-column prop="versionId" label="版本" width="80" /><el-table-column prop="sectionId" label="章节" width="80" /><el-table-column prop="lexicalScore" label="词法分" width="100" /><el-table-column prop="rerankScore" label="重排分" width="100" /><el-table-column label="采用" width="80"><template #default="{ row }">{{ row.selected ? '是' : '否' }}</template></el-table-column><el-table-column prop="discardReason" label="舍弃原因" min-width="180" /></el-table>
        <h3>最终回答</h3><pre class="trace-json">{{ selectedRetrievalLog.finalAnswer || '尚未生成回答' }}</pre>
      </template>
    </el-drawer>

    <el-drawer v-model="chatDetailOpen" size="760px" title="聊天详情">
      <div ref="chatDetailBodyRef" v-loading="chatDetailLoading" class="chat-detail-body">
      <template v-if="chatDetail">
        <section class="chat-summary">
          <div><span>会话ID</span><strong>{{ chatDetail.session.id }}</strong></div>
          <div><span>用户</span><strong>{{ chatDetail.session.userName || '-' }}</strong></div>
          <div><span>手机号</span><strong>{{ chatDetail.session.userPhone || '-' }}</strong></div>
          <div><span>消息数</span><strong>{{ chatDetail.session.messageCount }}</strong></div>
        </section>

        <h3 class="detail-title">主题会话</h3>
        <el-collapse v-if="chatDetail.topics?.length" class="topic-summary-collapse">
          <el-collapse-item
            v-for="topic in chatDetail.topics"
            :key="topic.id"
            :title="`#${topic.id} ${topic.title} / ${topic.status}`"
          >
            <section class="topic-summary-card">
              <div class="topic-summary-meta">
                <span>核心问题：{{ topic.coreQuestion }}</span>
                <span>消息数：{{ topic.messageCount }}</span>
                <span>Token：{{ topic.tokenInput }}/{{ topic.tokenOutput }}</span>
                <span>额度：{{ topic.quotaDeducted ? '已计入' : '不计入' }}</span>
                <span>开始：{{ topic.startedAt }}</span>
                <span>结束：{{ topic.endedAt || '-' }}</span>
              </div>
              <template v-if="topic.summary">
                <pre>{{ topic.summary.summary }}</pre>
                <div class="topic-summary-fields">
                  <p v-if="topic.summary.mainEvents"><strong>主要事件</strong>{{ topic.summary.mainEvents }}</p>
                  <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>
              </template>
              <el-empty v-else description="该主题尚未沉淀摘要" :image-size="60" />
            </section>
          </el-collapse-item>
        </el-collapse>
        <el-empty v-else description="该会话暂无主题记录" :image-size="64" />

        <h3 class="detail-title">老师求助卡（{{ chatDetail.helpCards?.length || 0 }}）</h3>
        <el-collapse v-if="chatDetail.helpCards?.length" class="topic-summary-collapse">
          <el-collapse-item
            v-for="card in chatDetail.helpCards"
            :key="card.id"
            :title="`#${card.id} / 主题 #${card.topicSessionId} / ${card.copied ? '用户已复制' : '未复制'}`"
          >
            <section class="topic-summary-card">
              <div class="topic-summary-meta">
                <span>生成：{{ card.createdAt }}</span>
                <span>复制：{{ card.copiedAt || '-' }}</span>
                <span>来源：{{ card.source }}</span>
              </div>
              <pre>{{ card.content }}</pre>
            </section>
          </el-collapse-item>
        </el-collapse>
        <el-empty v-else description="该会话暂无用户生成的求助卡" :image-size="64" />

        <h3 class="detail-title">班级分享稿（{{ chatDetail.shareDrafts?.length || 0 }}）</h3>
        <el-collapse v-if="chatDetail.shareDrafts?.length" class="topic-summary-collapse">
          <el-collapse-item
            v-for="draft in chatDetail.shareDrafts"
            :key="draft.id"
            :title="`#${draft.id} / 主题 #${draft.topicSessionId} / ${draft.copied ? '用户已复制' : '未复制'}`"
          >
            <section class="topic-summary-card">
              <div class="topic-summary-meta">
                <span>生成：{{ draft.createdAt }}</span>
                <span>复制：{{ draft.copiedAt || '-' }}</span>
                <span>来源：{{ draft.source }}</span>
              </div>
              <pre>{{ draft.content }}</pre>
            </section>
          </el-collapse-item>
        </el-collapse>
        <el-empty v-else description="该会话暂无用户生成的班级分享稿" :image-size="64" />

        <h3 class="detail-title">完整对话</h3>
        <AdminPagination
          class="chat-detail-pagination top"
          :page="chatDetailMessagePager.page"
          :page-size="chatDetailMessagePager.pageSize"
          :total="chatDetailMessagePager.total"
          @change="changeChatDetailMessagePage"
        />
        <section v-loading="chatMessagesLoading" class="conversation-list">
          <article
            v-for="message in chatDetail.messages"
            :key="message.id"
            class="conversation-message"
            :class="message.role"
          >
            <div class="message-meta">
              <strong>{{ message.role === 'user' ? '用户' : '大本营答疑助手' }}</strong>
              <span>{{ message.createdAt }}</span>
            </div>
            <pre>{{ message.content }}</pre>
            <div class="message-extra">
              <span>状态：{{ message.messageStatus }}</span>
              <span v-if="message.tokenInput">输入 Token：{{ message.tokenInput }}</span>
              <span v-if="message.tokenOutput">输出 Token：{{ message.tokenOutput }}</span>
              <span v-if="message.responseTimeMs">耗时：{{ message.responseTimeMs }}ms</span>
            </div>
          </article>
        </section>
        <AdminPagination
          class="chat-detail-pagination"
          :page="chatDetailMessagePager.page"
          :page-size="chatDetailMessagePager.pageSize"
          :total="chatDetailMessagePager.total"
          @change="changeChatDetailMessagePage"
        />

        <h3 class="detail-title">关联 AI 请求</h3>
        <el-collapse>
          <el-collapse-item v-for="log in chatDetail.aiLogs" :key="log.id" :title="`请求 #${log.id} / ${log.status}`">
            <div class="ai-log-grid">
              <span>模型：{{ log.modelName || '-' }}</span>
              <span>知识库：{{ log.knowledgeIds || '-' }}</span>
              <span>命中数：{{ log.retrieveCount }}</span>
              <span>Token：{{ log.totalToken || '-' }}</span>
              <span>耗时：{{ log.costMs || '-' }}ms</span>
              <span>时间：{{ log.createdAt }}</span>
            </div>
            <section v-if="log.retrievedChunks?.length" class="retrieval-chunks">
              <article v-for="chunk in log.retrievedChunks" :key="`${log.id}-${chunk.index}`" class="retrieval-chunk">
                <div class="chunk-head">
                  <strong>#{{ chunk.index }} {{ chunk.knowledgeName || '未知知识库' }}</strong>
                  <span>{{ chunk.title || '未命名片段' }}</span>
                </div>
                <pre>{{ chunk.content }}</pre>
                <a v-if="chunk.sourceUrl" :href="chunk.sourceUrl" target="_blank" rel="noreferrer">查看来源</a>
              </article>
            </section>
            <el-empty v-else description="本条请求未保存召回片段" :image-size="64" />
            <pre class="prompt-preview">{{ log.prompt || '无 Prompt 记录' }}</pre>
            <p v-if="log.errorMessage" class="error-text">{{ log.errorMessage }}</p>
          </el-collapse-item>
        </el-collapse>
      </template>
      <el-empty v-else-if="!chatDetailLoading" description="聊天详情加载失败或暂无数据" :image-size="80" />
      </div>
    </el-drawer>

    <el-drawer v-model="aiLogDetailOpen" size="820px" title="AI 请求详情">
      <template v-if="selectedAiLog">
        <section class="chat-summary">
          <div><span>请求ID</span><strong>{{ selectedAiLog.id }}</strong></div>
          <div><span>会话ID</span><strong>{{ selectedAiLog.sessionId || '-' }}</strong></div>
          <div><span>消息ID</span><strong>{{ selectedAiLog.messageId || '-' }}</strong></div>
          <div><span>用户ID</span><strong>{{ selectedAiLog.userId || '-' }}</strong></div>
          <div><span>模型</span><strong>{{ selectedAiLog.modelName || '-' }}</strong></div>
          <div><span>状态</span><strong>{{ selectedAiLog.status }}</strong></div>
        </section>

        <section class="audit-detail-grid">
          <span>知识库：{{ selectedAiLog.knowledgeIds || '-' }}</span>
          <span>命中数：{{ selectedAiLog.retrieveCount }}</span>
          <span>片段数：{{ selectedAiLog.retrievedChunks?.length || 0 }}</span>
          <span>输入 Token：{{ selectedAiLog.inputToken || '-' }}</span>
          <span>输出 Token：{{ selectedAiLog.outputToken || '-' }}</span>
          <span>总 Token：{{ selectedAiLog.totalToken || '-' }}</span>
          <span>估算成本：{{ formatMoney(selectedAiLog.estimatedCost, selectedAiLog.currency || 'CNY') }}</span>
          <span>问题类型：{{ selectedAiLog.questionType || '-' }}</span>
          <span>知识命中：{{ selectedAiLog.knowledgeHit ? '是' : '否' }}</span>
          <span>路由原因：{{ selectedAiLog.routeReason || '-' }}</span>
          <span>耗时：{{ selectedAiLog.costMs || '-' }}ms</span>
          <span>时间：{{ selectedAiLog.createdAt }}</span>
        </section>

        <p v-if="selectedAiLog.errorMessage" class="error-text">{{ selectedAiLog.errorMessage }}</p>

        <h3 class="detail-title">召回知识片段</h3>
        <section v-if="selectedAiLog.retrievedChunks?.length" class="retrieval-chunks">
          <article
            v-for="chunk in selectedAiLog.retrievedChunks"
            :key="`${selectedAiLog.id}-${chunk.index}`"
            class="retrieval-chunk"
          >
            <div class="chunk-head">
              <strong>片段 {{ chunk.index }}/{{ selectedAiLog.retrievedChunks.length }}</strong>
              <span>{{ chunk.knowledgeName || '未知知识库' }}</span>
              <span v-if="chunk.knowledgeId">知识库ID：{{ chunk.knowledgeId }}</span>
            </div>
            <div class="chunk-title">{{ chunk.title || '未命名片段' }}</div>
            <pre>{{ chunk.content }}</pre>
            <a v-if="chunk.sourceUrl" :href="chunk.sourceUrl" target="_blank" rel="noreferrer">查看来源</a>
          </article>
        </section>
        <el-empty v-else description="本条请求未保存召回片段" :image-size="72" />

        <h3 class="detail-title">完整 Prompt</h3>
        <pre class="prompt-preview">{{ selectedAiLog.prompt || '无 Prompt 记录' }}</pre>
      </template>
    </el-drawer>
  </main>
</template>
