import type { YinianAuthSession, YinianConfigSnapshot, YinianHotel, YinianImageCaptcha, YinianLoginWithPasswordInput, YinianLoginWithSmsInput, YinianLocalSkill, YinianServerStatus, YinianSessionState, YinianSkillSyncResult, YinianUser, } from '../../shared/yinian'; import type { YinianControlPlane } from './control-plane'; import { getYinianStorage, type YinianStorage } from './storage'; const MOCK_USER: YinianUser = { id: 'user_demo_manager', name: '王管理员', phone: '13800000000', email: 'manager@yinian.local', }; const MOCK_HOTELS: YinianHotel[] = [ { id: 'workspace_hangzhou_ops', name: '智念企业组织空间', brand: '智念', }, { id: 'workspace_shanghai_growth', name: '智念增长组织空间', brand: '智念', }, ]; function createSession(user: YinianUser = MOCK_USER, currentHotelId = MOCK_HOTELS[0].id): YinianAuthSession { return { authenticated: true, user, hotels: MOCK_HOTELS, currentHotelId, accessTokenExpiresAt: Date.now() + 60 * 60 * 1000, }; } interface MockYinianControlPlaneOptions { storage?: YinianStorage; } export class MockYinianControlPlane implements YinianControlPlane { private readonly storage: YinianStorage; private currentSession: YinianSessionState = { authenticated: false }; private localSkills: YinianLocalSkill[] = []; constructor(options: MockYinianControlPlaneOptions = {}) { this.storage = options.storage ?? getYinianStorage(); } async getServerStatus(): Promise { return { mode: 'mock', reachable: true, checkedAt: Date.now(), message: '当前使用本地演示数据', }; } async createImageCaptcha(randomStr = crypto.randomUUID()): Promise { return { randomStr, image: '', mimeType: 'image/svg+xml', raw: { mock: true }, }; } async restoreSession(): Promise { const persisted = await this.storage.getSession(); if (!persisted || persisted.mode !== 'mock') { this.currentSession = { authenticated: false }; return this.currentSession; } this.currentSession = { authenticated: true, user: persisted.user, hotels: persisted.hotels, currentHotelId: persisted.currentHotelId, accessTokenExpiresAt: persisted.accessTokenExpiresAt, }; const registry = await this.storage.getSkillRegistry(persisted.currentHotelId); this.localSkills = registry && 'skills' in registry ? registry.skills : []; return this.currentSession; } async getSessionState(): Promise { if (!this.currentSession.authenticated) { return this.restoreSession(); } return this.currentSession; } async loginWithSms(input: YinianLoginWithSmsInput): Promise { const phone = input.phone.trim(); if (!phone || !input.code.trim()) { throw new Error('手机号和验证码不能为空'); } this.currentSession = createSession({ ...MOCK_USER, phone }); await this.persistCurrentSession(); return this.currentSession; } async loginWithPassword(input: YinianLoginWithPasswordInput): Promise { const account = input.account.trim(); if (!account || !input.password.trim()) { throw new Error('账号和密码不能为空'); } this.currentSession = createSession({ ...MOCK_USER, name: account === 'admin' ? 'NIANXX 实施' : MOCK_USER.name, email: account.includes('@') ? account : MOCK_USER.email, }); await this.persistCurrentSession(); return this.currentSession; } async logout(): Promise { if (this.currentSession.authenticated) { await this.storage.clearConfig(this.currentSession.currentHotelId); await this.storage.clearSkillRegistry(this.currentSession.currentHotelId); } await this.storage.clearSession(); this.currentSession = { authenticated: false }; this.localSkills = []; return this.currentSession; } async switchHotel(hotelId: string): Promise { if (!this.currentSession.authenticated) { throw new Error('请先登录'); } const hotel = MOCK_HOTELS.find((item) => item.id === hotelId); if (!hotel) { throw new Error('工作空间不存在或未开通'); } this.currentSession = { ...this.currentSession, currentHotelId: hotel.id, }; await this.persistCurrentSession(); const registry = await this.storage.getSkillRegistry(hotel.id); this.localSkills = registry && 'skills' in registry ? registry.skills : []; return this.currentSession; } async getConfigSnapshot(): Promise { if (!this.currentSession.authenticated) { throw new Error('请先登录'); } const currentHotelId = this.currentSession.currentHotelId; const hotel = this.currentSession.hotels.find((item) => item.id === currentHotelId) ?? this.currentSession.hotels[0]; const snapshot = { serverTime: Date.now(), user: this.currentSession.user, hotel, hotels: this.currentSession.hotels, entitlements: [ { skillId: 'data-check', name: '数据检查助手', version: '1.0.0', enabled: true, category: 'ota-monitoring', triggers: ['manual', 'scheduled'], lastRunAt: Date.now() - 48 * 60 * 1000, }, { skillId: 'workflow-check', name: '流程检查助手', version: '1.0.0', enabled: true, category: 'ops-automation', triggers: ['manual', 'scheduled'], lastRunAt: Date.now() - 55 * 60 * 1000, }, { skillId: 'daily-report', name: '日报生成助手', version: '0.1.0', enabled: true, category: 'reporting', triggers: ['manual', 'scheduled'], lastRunAt: Date.now() - 2 * 60 * 60 * 1000, }, { skillId: 'customer-reply-helper', name: '客户回复助手', version: '0.1.0', enabled: true, category: 'guest-comm', triggers: ['manual', 'reply'], }, ], notificationChannels: [ { id: 'ch_wecom_ops', kind: 'wecom', label: '业务通知群', recipient: '当前组织空间通知群', enabled: true, source: 'nianxx', }, { id: 'ch_desktop', kind: 'desktop', label: '本机桌面通知', recipient: '当前设备', enabled: true, source: 'kernel', }, ], featureFlags: { yinianTodayPage: true, autoUpdateSkills: true, reviewReplyHelper: true, }, uiPolicy: { defaultPage: 'today', showAdvancedSettings: false, }, }; await this.storage.setConfig(snapshot); return snapshot; } async syncSkills(): Promise { const config = await this.getConfigSnapshot(); const now = Date.now(); const previousRegistry = await this.storage.getSkillRegistry(config.hotel.id); const previousSkills = previousRegistry && 'skills' in previousRegistry ? previousRegistry.skills : this.localSkills; this.localSkills = config.entitlements.map((skill) => { const previous = previousSkills.find((item) => item.skillId === skill.skillId); return { skillId: skill.skillId, name: skill.name, version: skill.version, enabled: skill.enabled, installedAt: previous?.installedAt ?? now, lastSyncedAt: now, status: previous?.version === skill.version ? 'skipped' : previous ? 'updated' : 'installed', source: 'mock', bundleSha256: `mock-${skill.skillId}-${skill.version}`, }; }); await this.storage.setSkillRegistry({ hotelId: config.hotel.id, updatedAt: now, skills: this.localSkills, }); return { hotelId: config.hotel.id, syncedAt: now, skills: this.localSkills, }; } async listLocalSkills(): Promise { if (this.currentSession.authenticated) { const registry = await this.storage.getSkillRegistry(this.currentSession.currentHotelId); if (registry && 'skills' in registry) { this.localSkills = registry.skills; } } return this.localSkills; } async getSkillRegistry(hotelId?: string) { return this.storage.getSkillRegistry(hotelId); } private async persistCurrentSession(): Promise { if (!this.currentSession.authenticated) return; await this.storage.setSession({ mode: 'mock', user: this.currentSession.user, hotels: this.currentSession.hotels, currentHotelId: this.currentSession.currentHotelId, accessTokenExpiresAt: this.currentSession.accessTokenExpiresAt, updatedAt: Date.now(), }); } }