import type { AdminConfigFact, AdminConfigFacts, AdminConfigQuietHours } from "../../lib/types"; /** * 配置只读事实卡的**纯逻辑**(契约 v0.68 §4.10(a))。这里只做形状 → 文案的映射, * 不发请求、不碰 DOM,因此可以用 plain-node 检查跑(见 configFacts.test.ts)。 * * 三条不变量(与后端 admin_config_facts.go 同一口径): * 1. 值一律取后端给的**进程生效值**,本层不推算、不格式化数值(只补单位/说明); * 2. 「默认 / 已改」只由 `isDefault` 决定,**不**反推 `.env` 写过什么(面板读不到); * 3. 后端没给的键如实显示「未报告」,**绝不**编一个值出来。 */ /** 后端没给这条事实时的显示值(不是 0,也不是默认值)。 */ export const FACT_MISSING_VALUE = "未报告"; /** 记忆整理的 7 个键(与后端 configFactSpecs 的前 7 条同序)。 */ export const MEMORY_TIDY_KEYS: readonly string[] = [ "MEMORY_TIDY_ENABLED", "MEMORY_TIDY_INTERVAL_SEC", "MEMORY_TIDY_QUIET_START_HOUR", "MEMORY_TIDY_QUIET_END_HOUR", "MEMORY_TIDY_RECALL_THRESHOLD", "MEMORY_TIDY_SCAN_LIMIT", "MEMORY_TIDY_CANDIDATE_LIMIT", ]; /** 安静时段的两个键(后端事实表后两条)。 */ export const QUIET_HOURS_KEYS: readonly string[] = ["QUIET_START_HOUR", "QUIET_END_HOUR"]; export interface FactRow { key: string; /** 中文说明(这个键管什么)。 */ label: string; /** 显示值(生效值 + 单位)。 */ value: string; /** `"默认"` / `"已改"` / `"未报告"`。 */ state: string; } interface FactMeta { label: string; /** 值后缀(单位);空串表示不加。 */ unit?: string; } const FACT_META: Record = { MEMORY_TIDY_ENABLED: { label: "记忆整理总开关" }, MEMORY_TIDY_INTERVAL_SEC: { label: "整理周期", unit: " 秒" }, MEMORY_TIDY_QUIET_START_HOUR: { label: "整理低峰窗口起点", unit: " 时" }, MEMORY_TIDY_QUIET_END_HOUR: { label: "整理低峰窗口终点", unit: " 时" }, MEMORY_TIDY_RECALL_THRESHOLD: { label: "候选召回门(余弦)" }, MEMORY_TIDY_SCAN_LIMIT: { label: "每轮扫描上限" }, MEMORY_TIDY_CANDIDATE_LIMIT: { label: "每轮候选上限" }, QUIET_START_HOUR: { label: "安静时段起点(本地钟点)", unit: " 时" }, QUIET_END_HOUR: { label: "安静时段终点(本地钟点)", unit: " 时" }, }; /** factRows 把后端事实列表按给定键序映射成显示行;缺的键如实标「未报告」。 */ export function factRows(facts: AdminConfigFacts | null, keys: readonly string[]): FactRow[] { const byKey = new Map(); for (const f of facts?.facts ?? []) byKey.set(f.key, f); return keys.map((key) => { const meta = FACT_META[key]; const got = byKey.get(key); if (!got) { return { key, label: meta ? meta.label : key, value: FACT_MISSING_VALUE, state: FACT_MISSING_VALUE, }; } return { key, label: meta ? meta.label : key, value: `${got.value}${meta?.unit ?? ""}`, state: got.isDefault ? "默认" : "已改", }; }); } /** 记忆整理事实行(7 条)。 */ export function memoryTidyRows(facts: AdminConfigFacts | null): FactRow[] { return factRows(facts, MEMORY_TIDY_KEYS); } /** 安静时段事实行(2 条)。 */ export function quietHoursRows(facts: AdminConfigFacts | null): FactRow[] { return factRows(facts, QUIET_HOURS_KEYS); } /** 安静时段窗口的一句话摘要(后端 label + 跨午夜 / 已关闭 / 「0/0 = 未填」的如实补充)。 */ export function quietHoursSummary(qh: AdminConfigQuietHours | null | undefined): string { if (!qh) return FACT_MISSING_VALUE; if (qh.disabled) return "已关闭(安静时段从不生效)"; const label = qh.label || `${qh.startHour}:00-${qh.endHour}:00`; if (qh.fallbackToDefault) { return `${label}(两端写 0 = 未填,运行时回落到默认窗口)`; } return qh.startHour > qh.endHour ? `${label}(跨午夜)` : label; } /** * 安静时段的**用途文案**(主人裁决 10 的新语义)。 * ⚠️ 必须说清「只服务定时主动思考 → 主动搭话,不静默回复」—— * 写成「夜间全静默」是已废弃的旧实现语义,被这次改动明确否掉了。 */ export function quietHoursPurpose(): string { return "安静时段只服务「定时主动思考 → 主动搭话」:她在这个窗口里默认不主动找你。回复侧不受它约束——你任何时候说话,她都照常回,不会因为到了安静时段就被静默。群聊同样没有夜间静默。"; }