dsh-deepseek-web-login 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useRef","createElement"],"sources":["../src/file-picker.ts","../src/cookies.ts","../src/account-sync.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 浏览器侧的文件「打开 / 另存为」封装。\n *\n * 为什么需要单独一个模块:插件宿主跑在 Electron 的 **utility 进程**里,\n * 那里 `require('electron')` 只暴露 `net` 与 `systemPreferences`(见 net-diagnostics\n * 的能力探测),而 `dialog` 是**主进程**模块 —— 插件够不到。\n * 也就是说\"弹一个系统文件框让人选\"这件事,宿主侧做不到,\n * 只能由**渲染进程**(插件界面所在的地方)用 Chromium 自己的能力完成:\n *\n * 打开文件:`<input type=\"file\">` —— 任何 Chromium 都支持,最稳\n * 另存为: `window.showSaveFilePicker()` —— File System Access API,Electron 有实现\n * 拿真实路径:`window.__DSH_DESKTOP_FILE_PATH__` —— DSH 注入的 preload 桥\n * (`webUtils.getPathForFile`,注释写明\"只解析操作者选中的、真实落盘的 File\")\n *\n * 三者都做**能力探测 + 优雅回退**,绝不把功能锁死:拿不到就走宿主侧写文件(旧行为),\n * 并如实说明回退原因。\n *\n * 关于「凭证要不要经过 HTTP」这件事的取舍,见 accounts.ts 里\n * `exportAccountsToFile` 与 `exportAccounts` 的注释 —— 那里有完整理由。\n */\n\n/** DSH 注入的 preload 桥(宿主未注入时整个对象不存在)。 */\ninterface DesktopFilePathBridgeWindow {\n __DSH_DESKTOP_FILE_PATH__?: {\n /** 把用户**选中的** File 还原成真实磁盘路径;非磁盘来源的 File 会抛错。 */\n getPathForFile(file: File): string\n }\n showSaveFilePicker?: (options?: {\n suggestedName?: string\n types?: { description?: string; accept: Record<string, string[]> }[]\n }) => Promise<{\n name?: string\n createWritable: () => Promise<{ write: (data: Blob | string) => Promise<void>; close: () => Promise<void> }>\n }>\n}\n\ntype Win = DesktopFilePathBridgeWindow & Record<string, any>\n\nconst win = (): Win => globalThis as unknown as Win\n\n/**\n * 把界面选中的 File 还原成**真实磁盘路径**。\n *\n * 这一步的价值:拿到路径后,宿主可以**自己去读那个文件**,\n * 于是导入的凭证明文根本不经过 HTTP 请求体 —— 只是把\"文件在哪\"告诉宿主。\n * 桥不可用(宿主没注入 / 非磁盘来源的 File)就返回 undefined,调用方改用内容导入。\n */\nexport function resolvePickedPath(file: File | undefined | null, w: Win = win()): string | undefined {\n if (!file) return undefined\n try {\n const resolved = w.__DSH_DESKTOP_FILE_PATH__?.getPathForFile?.(file)\n return typeof resolved === 'string' && resolved.trim() ? resolved : undefined\n } catch {\n // 桥在、但这个 File 不是磁盘来源(比如拖进来的字符串):不该因此把导入弄崩\n return undefined\n }\n}\n\n/** 渲染进程是否支持 File System Access 的「另存为」。 */\nexport function canSaveWithPicker(w: Win = win()): boolean {\n return typeof w?.showSaveFilePicker === 'function'\n}\n\n/**\n * 备份文件建议名。\n *\n * 刻意只用 **数字 / 字母 / 短横线 / 点**:这个字符串会进系统另存为框的\n * `suggestedName`,冒号、斜杠在 Windows 上是非法文件名字符(`toISOString()` 恰好\n * 带冒号,直接拿来当文件名会踩坑)。\n */\nexport function suggestedExportName(now: Date = new Date()): string {\n const pad = (value: number) => String(value).padStart(2, '0')\n const stamp =\n `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +\n `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`\n return `deepseek-accounts-${stamp}.json`\n}\n\nexport type SaveOutcome =\n | { kind: 'saved'; name: string }\n | { kind: 'cancelled' }\n /** 渲染进程没有 File System Access;调用方回退到宿主写文件。 */\n | { kind: 'unsupported' }\n /** 弹框过程本身失败(权限/平台差异等);调用方回退并说明原因。 */\n | { kind: 'failed'; reason: string }\n\n/** 用户取消选择时 Chromium 抛的是 AbortError,要和其他失败区分开(取消不是错误)。 */\nexport function isPickerCancel(error: unknown): boolean {\n return (error as any)?.name === 'AbortError'\n}\n\n/**\n * 弹系统「另存为」,并把 `produce()` 产出的文本写进用户选定的位置。\n *\n * ⚠️ 为什么收一个回调而不是直接收文本:**必须先弹框、再取内容**。\n * Chromium 的瞬时用户激活是有时限的,若调用方先 `await` 一次网络请求\n * (取备份内容)再弹框,很可能被判成\"没有用户手势\"而直接抛错。\n * 把取数包成回调,顺序就锁在这个函数里,调用方想写错都难。\n */\nexport async function saveWithPicker(\n suggestedName: string,\n produce: () => Promise<string>,\n w: Win = win(),\n): Promise<SaveOutcome> {\n if (!canSaveWithPicker(w)) return { kind: 'unsupported' }\n // ⚠️ 要留住 writable 的引用(审计 F22):`write`/`close` 失败时如果不 `abort`,\n // Chromium 会留下一个**未提交的临时文件与句柄**(模拟磁盘写满时实测 abort 从未被调用)。\n // 成功路径走完就把引用清掉,免得 catch 里对已关闭的 writable 再动手。\n let writable:\n | { write(data: Blob | string): Promise<void>; close(): Promise<void>; abort(reason?: unknown): Promise<void> }\n | undefined\n try {\n const handle = await w.showSaveFilePicker!({\n suggestedName,\n types: [{ description: 'JSON 备份', accept: { 'application/json': ['.json'] } }],\n })\n const text = await produce()\n writable = (await handle.createWritable()) as NonNullable<typeof writable>\n await writable.write(new Blob([text], { type: 'application/json' }))\n await writable.close()\n writable = undefined\n return { kind: 'saved', name: typeof handle?.name === 'string' && handle.name ? handle.name : suggestedName }\n } catch (error: any) {\n if (writable) {\n try {\n await writable.abort(error)\n } catch {}\n }\n if (isPickerCancel(error)) return { kind: 'cancelled' }\n return { kind: 'failed', reason: `${error?.name ?? ''} ${error?.message ?? error}`.trim() }\n }\n}\n\n/**\n * 弹系统「打开」框选一个 JSON。返回 undefined 表示用户取消。\n *\n * 用 `<input type=\"file\">` 而不是 `showOpenFilePicker`:前者在**任何** Chromium\n * 环境里都可用(包括跨域 iframe,Chromium 150 起第三方 iframe 是被禁止弹\n * File System Access 文件框的),是我们最可靠的\"让人选文件\"手段。\n */\nexport function pickJsonFile(doc: Document = document): Promise<File | undefined> {\n return new Promise((resolve) => {\n const input = doc.createElement('input')\n input.type = 'file'\n input.accept = '.json,application/json'\n input.style.display = 'none'\n let settled = false\n // R9(0.2.0):兜底超时 —— 没有 cancel 事件的旧环境里,用户关掉选择框后 promise\n // 会永远挂着(旧注释自己承认\"靠它挂着\")。10 分钟足够任何合理操作,超时按取消处理。\n const timer = setTimeout(() => finish(undefined), 10 * 60_000)\n const finish = (file: File | undefined) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n input.remove()\n resolve(file)\n }\n input.addEventListener('change', () => finish(input.files?.[0]))\n // Chromium 113+ 在用户关掉选择框时会发 cancel;没这个事件的环境就靠它挂着\n input.addEventListener('cancel', () => finish(undefined))\n doc.body.appendChild(input)\n input.click()\n })\n}\n\n/** 导入文件的大小上限:与宿主侧一致(实测单账号 ~1.7 KB、账号数上限 500 → ~850 KB,\n * 取 2 MiB 留 2 倍余量)。两边都要有 —— 前端挡是为了不白传,宿主挡才是真边界。 */\nexport const IMPORT_FILE_LIMIT_BYTES = 2 * 1024 * 1024\n\nexport type ImportSource =\n /** 拿到了真实路径:宿主自己读文件,内容不进 HTTP。 */\n | { kind: 'path'; path: string; name: string }\n /** 没拿到路径:退回\"界面读内容再交给宿主\"。 */\n | { kind: 'content'; payload: unknown; name: string }\n | { kind: 'unreadable'; name: string; reason: string }\n\n/**\n * 把用户选中的文件变成\"宿主能用的东西\"。\n *\n * 优先走路径(凭证不进 HTTP),拿不到路径才退回内容 —— 后者是必要的兜底:\n * 宿主没注入路径桥时功能不该整体失效。\n */\nexport async function readImportSource(file: File, w: Win = win()): Promise<ImportSource> {\n const name = file.name || '备份文件'\n // 前端先按大小挡一道(审计 F16):与宿主侧的上限保持一致,避免白传一次才发现太大。\n if (typeof file.size === 'number' && file.size > IMPORT_FILE_LIMIT_BYTES) {\n return {\n kind: 'unreadable',\n name,\n reason: `文件 ${Math.ceil(file.size / 1024)} KiB,超过上限 ${Math.floor(IMPORT_FILE_LIMIT_BYTES / 1024 / 1024)} MiB`,\n }\n }\n const path = resolvePickedPath(file, w)\n if (path) return { kind: 'path', path, name }\n try {\n return { kind: 'content', payload: JSON.parse(await file.text()), name }\n } catch (error: any) {\n return { kind: 'unreadable', name, reason: `${error?.message ?? error}` }\n }\n}\n","/**\n * cookie 过期信息的采集与解读。\n *\n * ## 为什么值得做\n *\n * 插件存下来的凭证里只有 `name=value` 串,**过期时间一直被丢掉** —— 于是\"登录态会不会\n * 因为长期不用而失效\"这件事在界面上完全没有可观察的线索。\n *\n * 实测(2026-09-12,只读、零额度):DeepSeek 的只读端点**不返回 set-cookie**,\n * token 也不轮换,所以**没有任何\"续期\"手段可做**;能做的只有**把过期时间采下来**,\n * 让\"不确定\"缩小一格 —— 至少能回答\"这些 cookie 里哪些本来就是会话级\"。\n *\n * 顺带一个实测结论(决定\"看什么\"):真正鉴权用的是 **`token`**,不是 cookie ——\n * 只发 token 不带 cookie 能通过,只发 cookie 不带 token 直接被拒(40002 Missing Token)。\n * 所以 cookie 的过期时间**不是**登录态寿命的上界;它只告诉我们浏览器侧会先丢哪些。\n *\n * ## 两种来源形状不同,都要认\n *\n * | 来源 | 字段 | 会话级的表示 |\n * | --- | --- | --- |\n * | CDP `Storage.getCookies`(真实 Edge/Chrome) | `expires` | `-1` |\n * | Electron `session.cookies.get()`(插件自开窗口) | `expirationDate` | 字段缺失 |\n *\n * 两者都以 **秒** 为单位(Unix epoch),本模块统一换算成**毫秒**。\n */\n\nexport interface CookieMeta {\n /** cookie 名。 */\n name: string\n /** 所属域(原样保留,便于排查域匹配问题)。 */\n domain: string\n /** 会话级:浏览器关闭即失效(服务端会话另有寿命,不受它约束)。 */\n session: boolean\n /** 持久级 cookie 的到期时间(**毫秒** epoch);会话级或未知 → 不出现该字段。 */\n expiresAt?: number\n}\n\n/**\n * 归一化一个 cookie 的过期形态。两种来源的字段名不同,这里统一。\n *\n * 判定顺序:显式 `session === true` 优先;否则看 `expires` / `expirationDate`。\n * 缺失、非数、`<= 0` **一律当会话级** —— CDP 对会话级给的就是 `-1`,\n * 而\"0\"在 Unix epoch 里也毫无意义,当成有效时间会算出 1970 年。\n */\nexport function readCookieExpiry(raw: any): { session: boolean; expiresAt?: number } {\n if (!raw || typeof raw !== 'object') return { session: true }\n if (raw.session === true) return { session: true }\n const seconds = Number(raw.expires ?? raw.expirationDate)\n if (!Number.isFinite(seconds) || seconds <= 0) return { session: true }\n return { session: false, expiresAt: Math.round(seconds * 1000) }\n}\n\n/**\n * 从任意来源的 cookie 数组里挑出目标域的,转成 `CookieMeta`。\n *\n * ⚠️ `filter` 由调用方给出、且**必须与该处拼 cookie 头的过滤条件逐字一致**:\n * 这些元信息要描述的正是\"请求里实际带上的那批 cookie\"。两条捕获路径原本的过滤条件\n * 不同(真实浏览器那条用 `deepseek`,Electron 那条用 `deepseek.com`),\n * 这里刻意不强行统一 —— 统一就等于改了请求头内容,而那个改动是没法靠单测兜住的。\n */\nexport function pickCookieMeta(\n cookies: readonly any[],\n filter: (domain: unknown) => boolean,\n): CookieMeta[] {\n const out: CookieMeta[] = []\n for (const raw of cookies ?? []) {\n const name = typeof raw?.name === 'string' ? raw.name : ''\n if (!name) continue\n const domain = String(raw?.domain ?? '')\n if (!filter(domain)) continue\n const { session, expiresAt } = readCookieExpiry(raw)\n out.push({ name, domain, session, ...(expiresAt !== undefined ? { expiresAt } : {}) })\n }\n return out\n}\n\n/**\n * 规整\"已经归一化过\"的 `CookieMeta` 数组(从磁盘读账号记录时用)。\n * 形状不对的条目直接丢掉,不抛错 —— 一份坏记录不该让整个账号库读不出来。\n */\nexport function normalizeCookieMetaList(raw: unknown): CookieMeta[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const out: CookieMeta[] = []\n for (const item of raw) {\n if (!item || typeof item !== 'object') continue\n const name = typeof (item as any).name === 'string' ? (item as any).name : ''\n if (!name) continue\n const expiresRaw = Number((item as any).expiresAt)\n const hasExpiry = Number.isFinite(expiresRaw) && expiresRaw > 0\n const session = (item as any).session === true || !hasExpiry\n out.push({\n name,\n domain: typeof (item as any).domain === 'string' ? (item as any).domain : '',\n session,\n ...(session ? {} : { expiresAt: Math.round(expiresRaw) }),\n })\n }\n return out.length > 0 ? out : undefined\n}\n\nexport interface CookieLifeSummary {\n total: number\n sessionCount: number\n persistentCount: number\n /** 持久级里**最晚**到期的那个 —— 用来回答\"浏览器侧还能撑多久\"。 */\n latest?: { name: string; expiresAt: number; daysLeft: number }\n}\n\n/**\n * 汇总结论。**没有记录时返回 `undefined`**(而不是全 0 的对象)——\n * 调用方要区分\"没采到过期信息\"(老记录 / 手动粘 token)和\"采到了、全是会话级\",\n * 这两种情况的界面文案完全不同。\n */\nexport function summarizeCookieLife(\n metas: readonly CookieMeta[] | undefined,\n now: number = Date.now(),\n): CookieLifeSummary | undefined {\n const list = metas ?? []\n if (list.length === 0) return undefined\n const persistent = list.filter((item) => !item.session && Number.isFinite(item.expiresAt))\n let latest: CookieLifeSummary['latest']\n for (const item of persistent) {\n const expiresAt = item.expiresAt as number\n if (!latest || expiresAt > latest.expiresAt) {\n latest = { name: item.name, expiresAt, daysLeft: (expiresAt - now) / 86_400_000 }\n }\n }\n return {\n total: list.length,\n sessionCount: list.length - persistent.length,\n persistentCount: persistent.length,\n ...(latest ? { latest } : {}),\n }\n}\n\n/** 剩余时间的口语化写法。 */\nexport function describeRemaining(daysLeft: number): string {\n if (daysLeft <= 0) return '已过期'\n if (daysLeft >= 1) return `还剩 ${Math.floor(daysLeft)} 天`\n const hours = Math.max(1, Math.round(daysLeft * 24))\n return `还剩 ${hours} 小时`\n}\n\n/**\n * 一句话说清 cookie 的寿命构成(界面直接展示)。\n *\n * 三种情况分开写,因为它们的含义完全不同:\n * - 没记录 → 老记录 / 手动粘 token,下次重新登录会补上;\n * - 全是会话级 → 浏览器侧本来就没有到期时间,看它撑多久没意义;\n * - 有持久级 → 给出最晚那个的剩余时间(**只是浏览器侧的上界,不是登录态寿命**)。\n */\nexport function describeCookieLife(\n summary: CookieLifeSummary | undefined,\n now: number = Date.now(),\n): string {\n if (!summary) return '⚠️ 未记录(重新登录后会补上)'\n const parts = [`${summary.total} 项`]\n parts.push(summary.sessionCount > 0 ? `${summary.sessionCount} 会话级` : '无会话级')\n if (summary.persistentCount > 0) parts.push(`${summary.persistentCount} 持久级`)\n if (summary.latest) {\n // 注意:`summary.latest.daysLeft` 是按生成这份 summary 的时刻算的。\n // 调用方若隔了很久才展示,传 `now` 进来重算即可。\n const daysLeft = (summary.latest.expiresAt - now) / 86_400_000\n parts.push(`${summary.latest.name} ${describeRemaining(daysLeft)}`)\n }\n return parts.join(' · ')\n}\n","/**\n * 账号库的「什么时候该重读」判据 + 内容签名(纯函数,无依赖,便于单测)。\n *\n * ── 为什么要单独有这么一个模块(2026-09-15 用户反馈)──\n *\n * 现象:**新登录一个账号后,账号库列表不会自己更新**,要关掉设置页再打开才看得到。\n *\n * 根因:账号库列表**不在状态轮询(`/status`)里**。面板每 2 秒轮询一次,但只读\n * `/status` 并渲染**当前账号**的信息;列表本身只有 `loadAccounts()` 被显式调用的那几个\n * 时刻才重读(面板初始化 / 切号 / 移除 / 重命名 / 重新登录 / 添加 / 导入)。\n *\n * 而登录捕获**是异步落地的**:\n * - CDP 那条路(宿主在 utility 进程时的常态)要等用户在浏览器里登录完;\n * - 能开 Electron 窗口那条路更直接 —— `openLoginWindow()` 是\"开窗即返回\",\n * 捕获发生在响应之后。\n * ⇒ 「加完账号」的捕获很可能晚于那次 `loadAccounts()`,列表就停在旧快照上,\n * 而且**此后不会再更新**(轮询不碰账号库)。\n *\n * 最直接的证据是代码里一句注释(client 的「登录新账号」处理里):\n * 「期间保持轮询更密一点,万一捕获是异步落地的也能及时刷出来」\n * —— 作者本来就指望轮询兜住这件事,但轮询只读 `/status`,那个兜底**从未生效**。\n *\n * 同源的第二个缺口:探活会更新记录(补上账号名、限制状态、失败标记),\n * 而**账号名是捕获之后由校验拿到的**(见 index.ts 里那段回写),\n * 所以\"列表里只有一串 `acc_xxxxxxxx`\"这件事也要等下一次刷新才消失。\n *\n * 所以修法是:把轮询的兜底做成真的 —— 按下面的节拍重读 `/accounts`,\n * 并用 `accountsSignature()` 判断内容是否真的变了(没变就不重建 DOM,\n * 免得每几秒把用户正在点的按钮换掉)。\n */\n\n/** 登录流程进行中(窗口开着 / 刚操作过)的重读间隔:够快,用户看不到\"没更新\"。 */\nexport const ACCOUNTS_SYNC_ACTIVE_MS = 3_000\n\n/** 空闲时的重读间隔:只为让探活补上的账号名 / 限制状态 / 失败标记自己出现,不必太快。 */\nexport const ACCOUNTS_SYNC_IDLE_MS = 30_000\n\n/**\n * 现在该不该重读账号库。\n *\n * @param now 当前时间戳\n * @param lastSyncedAt 上次重读的时刻(从未读过传 0)\n * @param active 登录流程是否进行中(窗口开着,或在操作后的加速窗口内)\n */\nexport function shouldSyncAccounts(now: number, lastSyncedAt: number, active: boolean): boolean {\n const gap = active ? ACCOUNTS_SYNC_ACTIVE_MS : ACCOUNTS_SYNC_IDLE_MS\n return now - lastSyncedAt >= gap\n}\n\n/**\n * 账号库响应的内容签名 —— 变了才重建列表。\n *\n * 只取**界面真正渲染的东西**(账号数组 + 分组定义 + 分组分区 + 当前账号 id),不取 `footprint`:\n * 后者是给台账页用的文件统计,跟列表无关,把它算进来只会平白触发重建。\n *\n * ⚠️ `groups` / `sections` 必须是**顶层字段显式列进来**:整包 stringify 只覆盖这里写出的键,\n * 漏一个就会出现\"新建了组、列表却不刷新\"—— 与 0.1.63(读错字段名)和 0.1.67(数据压根不在\n * `/status` 里)是同一类坑:**界面渲染什么,签名就得覆盖什么。**\n *\n * 整包 `JSON.stringify` 而不是逐个字段挑:**按构造就是完整的** ——\n * 将来 `/accounts` 多返回一个会被渲染的字段,这里不用改也不会漏。\n * (代价:若哪天往 accounts 里塞了每调用一次都不同的值,会变成每轮都重建;\n * 真出现那种字段,应该在这里显式排除。)\n */\nexport function accountsSignature(payload: unknown): string {\n const data = (payload ?? {}) as { activeId?: unknown; accounts?: unknown; groups?: unknown; sections?: unknown }\n const accounts = Array.isArray(data.accounts) ? data.accounts : []\n const groups = Array.isArray(data.groups) ? data.groups : []\n const sections = Array.isArray(data.sections) ? data.sections : []\n try {\n return JSON.stringify({ activeId: data.activeId ?? null, accounts, groups, sections })\n } catch {\n // 序列化失败时给空串:调用方约定\"空签名 = 每次都重建\",宁可重建也别卡住不更新\n return ''\n }\n}\n","/**\n * dsh-deepseek-web-login — client 设置页(slots: settings.section)。\n *\n * 与 host 通过同源 fetch 通信:/deepseek-web-login/api/*\n * 渲染契约(沿用生态实测结论):槽位 register 必须带 name 字段,\n * 渲染函数返回 React 元素(createElement),React #130 的坑即来自返回非元素。\n */\nimport { createElement, useEffect, useRef, useState } from 'react'\nimport {\n pickJsonFile,\n readImportSource,\n saveWithPicker,\n suggestedExportName,\n} from '../file-picker.ts'\nimport { describeCookieLife, summarizeCookieLife } from '../cookies.ts'\nimport { accountsSignature, shouldSyncAccounts } from '../account-sync.ts'\n\ntype ClientContext = {\n slots: any\n effect: any\n}\n\nexport const inject = ['slots']\n\nconst API = '/deepseek-web-login/api'\n\ninterface StatusPayload {\n provider: string\n registeredProviders?: string[]\n electron: boolean\n loginCapability?: { processType: string; canOpenWindow: boolean; browser: string | null }\n loginWindowOpen: boolean\n loginProgress: { open: boolean; startedAt?: string; captured?: { token: boolean; cookie: boolean; fingerprint: boolean; wasm: boolean }; lastError?: string; finished?: boolean }\n fingerprint?: { at: string; url: string; stripped: string[]; pageUa?: string; pageBrands?: string[]; pageWebdriver?: boolean }\n lastLoginResult?: { ok: boolean; message: string; at: string }\n auth: {\n loggedIn: boolean\n display?: string\n capturedAt?: string\n hasCookie: boolean\n hasFingerprint: boolean\n wasmHost?: string\n unverified?: boolean\n tokenLength?: number\n /** 观测到的账号级限制解除时间(毫秒时间戳)。只在**生成请求被拒**时才学到。 */\n limitUntilMs?: number | null\n limitObservedAt?: string | null\n /** 最近一次主动探活成功的时间(ISO)。 */\n lastVerifiedAt?: string | null\n /** 最近一次主动探活失败。 */\n lastVerifyError?: { at: string; message: string } | null\n /** cookie 的过期构成(捕获时记下)。老记录 / 手动粘 token 时不存在。 */\n cookieLife?: {\n total: number\n sessionCount: number\n persistentCount: number\n latest?: { name: string; expiresAt: number; daysLeft: number }\n }\n }\n validation?: { ok: boolean; error?: string }\n /** 插件的本地状态目录(「关于」页展示用)。 */\n paths?: { webLogin?: string; accounts?: string; ledger?: string }\n models: { id: string; name: string; description: string; modelType: string; thinking: boolean; contextWindow: number }[]\n config: {\n maxPromptChars: number\n idleTimeoutMs: number\n deleteWebSessions: boolean\n allowConcurrent?: boolean\n minRequestIntervalMs?: number\n version?: string\n probeIntervalMs?: number\n }\n}\n\nasync function api(path: string, init?: RequestInit): Promise<any> {\n const response = await fetch(API + path, {\n headers: { 'content-type': 'application/json' },\n ...init,\n })\n return await response.json()\n}\n\n/**\n * 样式表 —— 全部走宿主的主题令牌(`--dsw-alias-*`)。\n *\n * 历史坑(2026-09-12 用户实测):以前用的是 `var(--theme-text, #ddd)` 这套**并不存在**的变量名,\n * 于是每个颜色都落到兜底值上;兜底又全是深色 → 浅色主题下整个面板仍是黑底,\n * 灰色文字贴在深底上几乎读不出来。\n *\n * 现在的做法:\n * 1. 颜色优先取宿主令牌(`--dsw-alias-label-primary` / `bg-layer-*` / `border-l*` / `state-*` …),\n * 这些令牌由 DSH 按当前主题(浅 / 深 / 皮肤)重定义,插件无需自己判断主题;\n * 2. 令牌缺失时(老宿主 / 令牌改名)退回 `--fb-*`,而 `--fb-*` 由 `prefers-color-scheme` 切换,\n * 保证「浅色主题不会出现黑界面」这条底线永远成立;\n * 3. 正文改用系统无衬线字体(原来是等宽字体铺满全屏,观感像终端日志),等宽只留给命令与代码。\n */\nconst styles = `\n.dsw-page{\n/* 兜底色:令牌缺失时才用到,随系统主题切换 */\n--fb-fg:#1f2329;--fb-fg2:#545b66;--fb-fg3:#8b93a3;\n--fb-bg1:#f6f7f9;--fb-bg2:#ffffff;--fb-bd:#e8eaee;--fb-bd2:#d7dae0;\n--fb-hover:#0000000f;--fb-code:#f3f4f6;\n/* 语义层 */\n--fg:var(--dsw-alias-label-primary,var(--fb-fg));\n--fg2:var(--dsw-alias-label-secondary,var(--fb-fg2));\n--fg3:var(--dsw-alias-label-tertiary,var(--fb-fg3));\n--bg1:var(--dsw-alias-bg-layer-1,var(--fb-bg1));\n--bg2:var(--dsw-alias-bg-layer-2,var(--fb-bg2));\n--bd:var(--dsw-alias-border-l1,var(--fb-bd));\n--bd2:var(--dsw-alias-border-l2,var(--fb-bd2));\n--hover:var(--dsw-alias-interactive-bg-hover,var(--fb-hover));\n--code-bg:var(--dsw-alias-markdown-code-block,var(--fb-code));\n--accent:var(--dsw-alias-brand-primary,#3b6ef0);\n--on-accent:var(--dsw-alias-label-primary-foreground,#ffffff);\n--ok:var(--dsw-alias-state-success-primary,#1a9f5a);\n--warn:var(--dsw-alias-state-warn-label,#9a6a00);\n--err:var(--dsw-alias-state-error-primary,#d93025);\n--mono:var(--dsw-alias-font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);\nfont-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",sans-serif;\nfont-size:13px;line-height:1.6;color:var(--fg);max-width:760px;padding:2px 0 12px;\n-webkit-font-smoothing:antialiased;\n}\n@media (prefers-color-scheme:dark){\n.dsw-page{--fb-fg:#ededed;--fb-fg2:#b6bcc6;--fb-fg3:#858c99;\n--fb-bg1:#212121;--fb-bg2:#2a2a2a;--fb-bd:#333333;--fb-bd2:#3d3d3d;\n--fb-hover:#ffffff14;--fb-code:#1c1c1c}\n}\n.dsw-title{margin:0 0 3px;font-size:14px;font-weight:500;color:var(--fg)}\n.dsw-sub{margin:0 0 14px;font-size:12px;line-height:1.55;color:var(--fg3)}\n/* provider 名带连字符,万一折行会断成 deepseek- / web(看着像故障)—— 整词不拆 */\n.dsw-nobreak{white-space:nowrap}\n.dsw-card{background:var(--bg2);border:1px solid var(--bd);border-radius:12px;padding:14px 16px;margin-bottom:10px}\n.dsw-cardhead{display:flex;align-items:center;gap:8px;margin:0 0 8px;font-size:13px;font-weight:500;color:var(--fg)}\n.dsw-card > .name{margin:0 0 8px;font-size:13px;font-weight:500;color:var(--fg)}\n.dsw-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n.dsw-badge{font-size:11px;font-weight:500;line-height:1.7;padding:1px 9px;border-radius:999px;white-space:nowrap}\n.dsw-badge.on{color:var(--ok);background:rgba(26,159,90,.15)}\n.dsw-badge.off{color:var(--warn);background:rgba(200,140,20,.16)}\n.dsw-badge.err{color:var(--err);background:rgba(217,48,37,.15)}\n.dsw-btn{font:inherit;font-size:12px;font-weight:500;line-height:1.5;padding:6px 13px;border-radius:8px;\nborder:1px solid transparent;background:var(--accent);color:var(--on-accent);cursor:pointer;\ntransition:background-color .15s ease,border-color .15s ease,opacity .15s ease}\n.dsw-btn:hover:not(:disabled){opacity:.88}\n.dsw-btn:active:not(:disabled){opacity:.72}\n.dsw-btn:disabled{opacity:.4;cursor:not-allowed}\n.dsw-btn.ghost{background:transparent;border-color:var(--bd2);color:var(--fg)}\n.dsw-btn.ghost:hover:not(:disabled){background:var(--hover);opacity:1}\n.dsw-btn.danger{background:transparent;border-color:var(--bd2);color:var(--err)}\n.dsw-btn.danger:hover:not(:disabled){background:rgba(217,48,37,.1);border-color:var(--err);opacity:1}\n.dsw-btn.armed{background:var(--err);border-color:var(--err);color:#ffffff}\n.dsw-btn.armed:hover:not(:disabled){opacity:.9}\n.dsw-input,.dsw-area{width:100%;box-sizing:border-box;font:inherit;font-size:12.5px;\nbackground:var(--bg1);color:var(--fg);border:1px solid var(--bd2);border-radius:8px;padding:7px 10px;\noutline:none;transition:border-color .15s ease,box-shadow .15s ease}\n.dsw-input:focus,.dsw-area:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--hover)}\n.dsw-area{min-height:68px;resize:vertical;font-family:var(--mono);font-size:12px;line-height:1.5}\n.dsw-kv{display:grid;grid-template-columns:auto 1fr;gap:0 12px;margin:2px 0 0;align-items:baseline}\n.dsw-kv > *{padding:5px 0;border-top:1px solid var(--bd);min-width:0}\n.dsw-kv > :nth-child(1),.dsw-kv > :nth-child(2){border-top:none}\n.dsw-kv .k{color:var(--fg3);font-size:12px;white-space:nowrap}\n.dsw-kv > *:not(.k){color:var(--fg);word-break:break-word}\n.dsw-msg{margin-top:10px;padding:9px 12px;border-radius:8px;background:var(--bg1);border:1px solid var(--bd);\nborder-left:3px solid var(--fg3);white-space:pre-wrap;max-height:240px;overflow:auto;\nfont-size:12px;line-height:1.6;color:var(--fg)}\n.dsw-msg.err{border-left-color:var(--err);color:var(--err)}\n.dsw-msg.ok{border-left-color:var(--ok)}\n.dsw-models{list-style:none;margin:2px 0 0;padding:0}\n.dsw-models li{padding:9px 0;border-top:1px solid var(--bd)}\n.dsw-models li:first-child{border-top:none;padding-top:2px}\n.dsw-models .name{font-size:13px;font-weight:500;color:var(--fg)}\n.dsw-models .id{color:var(--fg3);font-size:12px;margin-top:1px;word-break:break-word}\n.dsw-hint{color:var(--fg3);font-size:12px;line-height:1.6;margin:8px 0 0}\n.dsw-gate-row{display:flex;align-items:center;gap:10px;margin:10px 0;flex-wrap:wrap}\n.dsw-gate-label{color:var(--fg3);font-size:12px;min-width:52px;flex:0 0 auto}\n.dsw-gate-value{font-size:12px;color:var(--fg);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n/* 「上下限」成对的一行:两个滑块并排,右边一个合成值(如「6~10 个」)。\n —— 比\"每个边界各占一行\"省一半高度,而且两个边界的相对位置一眼能比出来。 */\n.dsw-range-pair{flex:1 1 240px;display:flex;gap:8px;align-items:center;min-width:190px}\n.dsw-gate-pair-value{font-size:12px;color:var(--fg);min-width:78px;text-align:right;font-variant-numeric:tabular-nums}\n.dsw-switch{display:inline-flex;align-items:center;gap:9px;font-size:13px;color:var(--fg);cursor:pointer;user-select:none}\n.dsw-switch input{position:absolute;opacity:0;width:0;height:0}\n.dsw-switch-track{width:34px;height:20px;border-radius:999px;background:var(--bd2);position:relative;transition:background-color .15s ease;flex:0 0 auto}\n.dsw-switch-track::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s ease}\n.dsw-switch input:checked + .dsw-switch-track{background:var(--err)}\n.dsw-switch input:checked + .dsw-switch-track::after{transform:translateX(14px)}\n.dsw-range{-webkit-appearance:none;appearance:none;flex:1 1 160px;height:4px;border-radius:999px;background:var(--bd2);outline:none;cursor:pointer}\n.dsw-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;border:none}\n.dsw-preset{padding:4px 10px;font-size:12px}\n.dsw-preset.active{background:var(--accent);border-color:transparent;color:var(--on-accent)}\n.dsw-gate-msg{color:var(--fg3);min-height:1.6em}\n.dsw-code{display:block;margin:6px 0;padding:7px 10px;background:var(--code-bg);border:1px solid var(--bd);\nborder-radius:8px;font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--fg);\noverflow-x:auto;white-space:pre}\n.dsw-msg::-webkit-scrollbar,.dsw-code::-webkit-scrollbar,.dsw-area::-webkit-scrollbar{width:8px;height:8px}\n.dsw-msg::-webkit-scrollbar-thumb,.dsw-code::-webkit-scrollbar-thumb,.dsw-area::-webkit-scrollbar-thumb{\nbackground:var(--bd2);border-radius:4px}\n/* ── 标签页:把原来一页到底的 7 张卡拆成 4 页 ────────────────────────\n 标签栏做成带边框的圆角容器;未选中 70% 透明度 + 下划线指示当前页。\n 颜色全部走设计令牌,浅色/深色自动跟随,不用写第二份。 */\n.dsw-tabs{display:inline-flex;gap:4px;border:1px solid var(--bd);border-radius:8px;\npadding:0 10px;margin:4px 0 14px;background:var(--bg1)}\n.dsw-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;\npadding:8px 14px;font:inherit;font-size:13px;color:var(--fg2);opacity:.7;cursor:pointer;\ntransition:opacity .15s ease,border-color .15s ease}\n.dsw-tab:hover{opacity:1}\n.dsw-tab.active{opacity:1;color:var(--fg);border-bottom-color:var(--fg)}\n.dsw-pane[hidden]{display:none}\n/* 二级(子页面)标签:**分段胶囊**。\n 刻意和一级标签长得不一样(一级是\"下划线 + 大字号\"),这样一眼能看出自己在第几层;\n 否则两层长得一样、用户会以为回到了同一层。 */\n.dsw-subtabs{display:inline-flex;gap:2px;padding:3px;margin:0 0 14px;border-radius:999px;\n background:var(--bg1);border:1px solid var(--bd)}\n.dsw-subtab{appearance:none;border:0;background:transparent;font:inherit;font-size:12px;\n font-weight:500;color:var(--fg2);padding:4px 14px;border-radius:999px;cursor:pointer;\n transition:color .15s ease,background-color .15s ease}\n.dsw-subtab:hover{color:var(--fg)}\n/* 选中态:底色用 bg2 铺在 bg1 上,深色主题里这两档只差一点点,\n 所以必须再加一圈描边(bd2 比 bd 明显)才立得住 —— 实测深色下不加就跟没选中一样。 */\n.dsw-subtab.active{background:var(--bg2);color:var(--fg);font-weight:600;\n box-shadow:inset 0 0 0 1px var(--bd2)}\n/* 调用台账 */\n.dsw-spark{display:block;width:100%;height:40px;margin:8px 0 2px}\n.dsw-spark rect{fill:var(--bd2)}\n.dsw-spark rect.on{fill:var(--accent)}\n.dsw-spark rect.bad{fill:var(--err)}\n.dsw-path{font-family:var(--mono);font-size:11px;color:var(--fg2);word-break:break-all}\n/* 账号库 */\n.dsw-accounts{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}\n.dsw-account{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border:1px solid var(--bd);\nborder-radius:10px;padding:9px 11px;background:var(--bg1)}\n.dsw-account.active{border-color:var(--accent)}\n.dsw-account-main{flex:1 1 200px;min-width:0}\n.dsw-account-title{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:13px;font-weight:500}\n.dsw-account-meta{font-size:11px;color:var(--fg3);margin-top:3px;word-break:break-all}\n.dsw-account-fix{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:6px}\n.dsw-account-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}\n.dsw-labelinput{font:inherit;font-size:12px;padding:3px 7px;border-radius:6px;border:1px solid var(--bd2);\nbackground:var(--bg2);color:var(--fg);width:150px;margin-top:4px}\n.dsw-limit{color:var(--warn)}\n/* 分组:组标题行 + 组内缩进 + 归组下拉 */\n.dsw-grouphead{display:flex;align-items:center;gap:8px;margin:12px 0 0;font-size:12px}\n.dsw-grouphead .gname{font-weight:500}\n.dsw-grouphead .gcount{color:var(--fg3);font-size:11px}\n.dsw-grouphead .gtoggle{cursor:pointer;user-select:none;color:var(--fg2);width:12px;display:inline-block;text-align:center}\n.dsw-grouphead .gspacer{flex:1 1 auto}\n.dsw-grouphead .dsw-btn{font-size:11px;padding:1px 7px}\n.dsw-account.grouped{margin-left:14px}\n.dsw-groupsel{font:inherit;font-size:11px;padding:2px 4px;border-radius:6px;border:1px solid var(--bd2);\nbackground:var(--bg2);color:var(--fg);max-width:110px}\n/* 操作反馈:不归属任何一页,常驻在标签栏之上 */\n.dsw-alert{margin:0 0 12px;padding:8px 10px;border-radius:8px;border:1px solid var(--bd);\nbackground:var(--bg1);white-space:pre-wrap;font-size:12px}\n.dsw-alert.ok{border-left:3px solid var(--ok);color:var(--ok)}\n.dsw-alert.err{border-left:3px solid var(--err);color:var(--err)}\n`\n\n/** 短时间:今天只显示时分,其它显示月/日 时:分。 */\nfunction shortTime(input?: string | number | null): string {\n if (input === undefined || input === null || input === '') return ''\n const date = new Date(input)\n if (Number.isNaN(date.getTime())) return ''\n const now = new Date()\n const sameDay = date.toDateString() === now.toDateString()\n const hhmm = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n return sameDay ? hhmm : `${date.getMonth() + 1}/${date.getDate()} ${hhmm}`\n}\n\n/** 相对时间:刚刚 / N 分钟前 / N 小时前 / N 天前。 */\nfunction relTime(input?: string | null): string {\n if (!input) return ''\n const at = new Date(input).getTime()\n if (!Number.isFinite(at)) return ''\n const diff = Date.now() - at\n if (diff < 60_000) return '刚刚'\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`\n return `${Math.floor(diff / 86_400_000)} 天前`\n}\n\n/** 倒计时:还剩 2 小时 13 分 / 已解除。 */\nfunction countdown(untilMs?: number | null): string {\n if (!untilMs || !Number.isFinite(untilMs)) return ''\n const left = untilMs - Date.now()\n if (left <= 0) return '已解除'\n const totalMinutes = Math.ceil(left / 60_000)\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n if (hours > 0) return `剩余 ${hours} 小时 ${minutes} 分`\n return `剩余 ${minutes} 分`\n}\n\nfunction el(tag: string, cls?: string, text?: string): HTMLElement {\n const node = document.createElement(tag)\n if (cls) node.className = cls\n if (text !== undefined) node.textContent = text\n return node\n}\n\n/** 面板本体:命令式 DOM(生态既有插件同款做法),挂在 React 容器里。 */\nfunction Panel(): any {\n const hostRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n const root = hostRef.current\n if (!root) return\n let disposed = false\n let timer: number | undefined\n\n const style = document.createElement('style')\n style.textContent = styles\n\n const page = el('div', 'dsw-page')\n const title = el('h3', 'dsw-title', 'DeepSeek 网页登录(免费模型)')\n // 副标题的宽度是\"预算\"问题,不是随便写的:\n // 上一版(78 字)单行要 558px,而宿主面板的内容宽约 560px —— 正好压在折行边界上。\n // 「账号」页比「模型」页高,面板因此出现纵向滚动条,内容宽度少十几像素,\n // 最后一个词就掉到第二行,还断在 `deepseek-web` 的连字符处(看着像故障)。\n // 现在这版约 467px,留 ~90px 余量,切标签/缩放窗口都不会再翻行。\n // ⚠️ 以后改这句话,请保持单行宽度 ≲ 470px(量法:white-space:nowrap 的 span 取 getBoundingClientRect)。\n const sub = el('p', 'dsw-sub')\n sub.append('用 chat.deepseek.com 的登录态驱动 DSH,不需要 API Key(provider:')\n sub.append(el('span', 'dsw-nobreak', 'deepseek-web'))\n sub.append(')')\n page.append(style, title, sub)\n\n // ── 操作反馈:常驻在标签栏之上 ──────────────────────────────\n // 拆成标签页后,在「账号」页点按钮的反馈若落在别的页里就等于看不见,所以它不归属任何一页。\n const message = el('div', 'dsw-alert')\n message.style.display = 'none'\n page.append(message)\n\n const showMessage = (text: string, kind: 'ok' | 'err' | '' = ''): void => {\n message.textContent = text\n message.className = `dsw-alert${kind ? ` ${kind}` : ''}`\n message.style.display = 'block'\n }\n\n // ── 标签栏 + 四个页 ────────────────────────────────────────\n // 原来 7 张卡堆在一页,「找某一项」要滚很久。分组按\"什么时候会用它\":\n // 账号(登录/换号)· 模型(查阅与测试)· 防风控(限流与清理)· 传输层(指纹)\n const TAB_KEYS = ['account', 'model', 'gate', 'transport', 'context', 'about'] as const\n const TAB_LABELS: Record<string, string> = {\n account: '账号',\n model: '模型',\n gate: '防风控',\n transport: '传输层',\n context: '上下文',\n about: '关于',\n }\n const accountPane = el('div', 'dsw-pane')\n const modelPane = el('div', 'dsw-pane')\n const gatePane = el('div', 'dsw-pane')\n const transportPane = el('div', 'dsw-pane')\n const contextPane = el('div', 'dsw-pane')\n const aboutPane = el('div', 'dsw-pane')\n const panes: Record<string, HTMLElement> = {\n account: accountPane,\n model: modelPane,\n gate: gatePane,\n transport: transportPane,\n context: contextPane,\n about: aboutPane,\n }\n const tabButtons: Record<string, HTMLButtonElement> = {}\n const selectTab = (key: string): void => {\n for (const each of TAB_KEYS) {\n const on = each === key\n panes[each].hidden = !on\n tabButtons[each].classList.toggle('active', on)\n tabButtons[each].setAttribute('aria-selected', String(on))\n }\n }\n const tabBar = el('div', 'dsw-tabs')\n tabBar.setAttribute('role', 'tablist')\n for (const key of TAB_KEYS) {\n const btn = el('button', 'dsw-tab', TAB_LABELS[key]) as HTMLButtonElement\n btn.type = 'button'\n btn.setAttribute('role', 'tab')\n btn.addEventListener('click', () => selectTab(key))\n tabButtons[key] = btn\n tabBar.append(btn)\n panes[key].setAttribute('role', 'tabpanel')\n }\n page.append(tabBar, ...TAB_KEYS.map((key) => panes[key]))\n selectTab('account')\n\n // ── 账号页内部再分两个子页 ──────────────────────────────────────\n // 4 张卡堆在一页还是太长。按\"你是来看状态、还是来管账号\"分:\n // 登录状态 这个号现在能不能用 / 怎么再登一次\n // 账号库 我有哪些号 / 怎么加、切、删、导入导出\n // 子页签用**分段胶囊**,与一级的下划线标签在视觉上分层。\n const ACCT_KEYS = ['status', 'library'] as const\n const ACCT_LABELS: Record<string, string> = { status: '登录状态', library: '账号库' }\n const acctStatusPane = el('div', 'dsw-pane')\n const acctLibraryPane = el('div', 'dsw-pane')\n const acctPanes: Record<string, HTMLElement> = { status: acctStatusPane, library: acctLibraryPane }\n const acctButtons: Record<string, HTMLButtonElement> = {}\n const selectAcctTab = (key: string): void => {\n for (const each of ACCT_KEYS) {\n const on = each === key\n acctPanes[each].hidden = !on\n acctButtons[each].classList.toggle('active', on)\n acctButtons[each].setAttribute('aria-selected', String(on))\n }\n }\n const acctTabBar = el('div', 'dsw-subtabs')\n acctTabBar.setAttribute('role', 'tablist')\n for (const key of ACCT_KEYS) {\n const btn = el('button', 'dsw-subtab', ACCT_LABELS[key]) as HTMLButtonElement\n btn.type = 'button'\n btn.setAttribute('role', 'tab')\n btn.addEventListener('click', () => selectAcctTab(key))\n acctButtons[key] = btn\n acctTabBar.append(btn)\n acctPanes[key].setAttribute('role', 'tabpanel')\n }\n accountPane.append(acctTabBar, acctStatusPane, acctLibraryPane)\n // 和一级标签同一个原则:用 hidden 属性切,不靠 CSS 类 ——\n // 万一样式没加载,退化成\"两页都显示\"(难看但能用),而不是\"除了一页全空白\"。\n selectAcctTab('status')\n\n // ── 登录卡 ──\n const loginCard = el('div', 'dsw-card')\n const loginHead = el('div', 'dsw-cardhead')\n const loginTitle = el('div')\n loginTitle.append(el('span', 'name', '登录状态'))\n const badge = el('span', 'dsw-badge off', '⚪ 未登录')\n loginTitle.append(badge)\n loginHead.append(loginTitle)\n loginCard.append(loginHead)\n\n const statusKv = el('div', 'dsw-kv')\n loginCard.append(statusKv)\n\n // 账号级限制单独占一行 + 自己的倒计时。\n // 为什么要单独一行、还要挂定时器:这个状态**只在生成请求被拒时才能学到**\n // (受限期间 users/current 依然返回 200),所以不能指望\"刷新时顺便更新\",\n // 要让\"还剩多久\"一直显示着。\n let activeLimitUntilMs: number | null = null\n const limitRow = el('p', 'dsw-hint dsw-limit')\n limitRow.style.display = 'none'\n limitRow.style.marginTop = '8px'\n loginCard.append(limitRow)\n const paintLimit = (): void => {\n if (!activeLimitUntilMs) {\n limitRow.style.display = 'none'\n return\n }\n const text = countdown(activeLimitUntilMs)\n limitRow.style.display = ''\n limitRow.textContent =\n text === '已解除'\n ? `账号级限制已解除(${shortTime(activeLimitUntilMs)})。`\n : `⚠️ 账号级限制:${text}(${shortTime(activeLimitUntilMs)} 解除)—— 期间生成会被拒;只读调用(校验 / 探活)不受影响。`\n }\n\n // ── 账号卡(退出 / 换号)──\n // 为什么要单独一张卡:退出登录以前只作为一个按钮塞在「手动粘贴 Token」那张卡的角落里,\n // 用户根本找不到(实测反馈)。退出账号是高频操作,必须显眼、且要能换号。\n const accountCard = el('div', 'dsw-card')\n accountCard.append(el('div', 'dsw-cardhead', '当前账号'))\n const accountLine = el('div', 'dsw-kv')\n accountCard.append(accountLine)\n const accountActions = el('div', 'dsw-row')\n accountActions.style.marginTop = '8px'\n const logoutBtn = el('button', 'dsw-btn danger', '退出当前账号') as HTMLButtonElement\n const switchBtn = el('button', 'dsw-btn ghost', '退出并登录其它账号') as HTMLButtonElement\n accountActions.append(logoutBtn, switchBtn)\n accountCard.append(accountActions)\n const accountHint = el(\n 'p',\n 'dsw-hint',\n '⚠️ 「退出」= 把该账号从账号库移除,不是\"只登出\":本地凭证与浏览器登录态会一起清掉。' +\n '想留住它就先「导出备份」;只是想换个号用,去「账号库 → 登录新账号」。',\n )\n accountCard.append(accountHint)\n\n const loginActions = el('div', 'dsw-row')\n loginActions.style.marginTop = '10px'\n const browserBtn = el('button', 'dsw-btn', '浏览器窗口登录') as HTMLButtonElement\n const externalBtn = el('button', 'dsw-btn ghost', '用我的默认浏览器登录') as HTMLButtonElement\n const recoverBtn = el('button', 'dsw-btn ghost', '从已登录窗口恢复') as HTMLButtonElement\n const refreshBtn = el('button', 'dsw-btn ghost', '刷新状态') as HTMLButtonElement\n loginActions.append(browserBtn, externalBtn, recoverBtn, refreshBtn)\n loginCard.append(loginActions)\n loginCard.append(\n el(\n 'p',\n 'dsw-hint',\n '「用我的默认浏览器登录」= 用系统浏览器打开 chat.deepseek.com(网页端若提示「使用环境异常」,走这条)。' +\n '外部浏览器的登录态插件抓不到,所以要用 F12 控制台取 token 粘到下面那张卡(命令已备好)。',\n ),\n )\n loginCard.append(\n el(\n 'p',\n 'dsw-hint',\n '「从已登录窗口恢复」= 复用上次登录过的窗口分区直接取凭证(免重新登录),凭证丢失时用它救急。',\n ),\n )\n acctStatusPane.append(loginCard, accountCard)\n\n // ── 账号库:多账号并存 + 一键切换 ────────────────────────────────\n // 为什么值得有:以前换号的代价是「退出 → 清浏览器分区 → 重新登录 → 等捕获」,\n // 期间原来的号也回不去了。现在保存过的账号都在库里,切换即时生效。\n //\n // ⚠️ 风险已在 src/accounts.ts 的模块注释里写明,这里再对使用者说一遍:\n // 用多账号轮换规避单账号限流,会被服务商把账号关联起来,处置通常更重。\n // 所以刻意**只提供手动切换、不做自动轮换** —— 真人不会几分钟换一个号接着发。\n /**\n * 一句话反馈节点:给它的 `textContent` 赋值时**自动选样式** ——\n * 命中「失败/错误/无法」→ 红框红字(醒目),以「已…」开头 → 绿框,其余 → 灰色小字。\n *\n * 为什么自动判定、而不是让每个调用点传 kind(2026-09-15 用户反馈):\n * 「账号切换失败」原来和旁边的说明文字长得一模一样(都是灰色小字),根本注意不到。\n * 这三个节点加起来 40 多个赋值点,靠人手传 kind 迟早会漏 —— 而漏掉的那个往往正是报错。\n */\n const createMsgNode = (): { node: HTMLElement; textContent: string } => {\n const node = el('p', 'dsw-hint dsw-gate-msg', '')\n let text = ''\n return {\n node,\n get textContent(): string {\n return text\n },\n set textContent(value: string) {\n text = value ?? ''\n node.textContent = text\n node.className = /失败|错误|无法|不对|⚠️/.test(text)\n ? 'dsw-msg err'\n : /^(✅|已切换|已移除|已捕获|已保存|已原地)/.test(text)\n ? 'dsw-msg ok'\n : 'dsw-hint dsw-gate-msg'\n },\n }\n }\n\n const accountsCard = el('div', 'dsw-card')\n const accountsHead = el('div', 'dsw-cardhead')\n accountsHead.append(el('span', 'name', '账号库'))\n const accountsBadge = el('span', 'dsw-badge off', '—')\n accountsHead.append(accountsBadge)\n accountsCard.append(accountsHead)\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 保存过的账号都在本机,点「切换」即时生效、不用重新登录 —— 切换后从下一次请求开始生效。',\n ),\n )\n\n const accountsList = el('ul', 'dsw-accounts')\n accountsCard.append(accountsList)\n const accountsEmpty = el('p', 'dsw-hint', '账号库是空的 —— 用上面的「登录」或「手动粘贴 Token」添加一个。')\n accountsCard.append(accountsEmpty)\n\n const accountsIOPanel = el('div', 'dsw-row')\n accountsIOPanel.style.marginTop = '10px'\n // 「登录新账号」:以前账号库**根本没有\"再加一个号\"的入口** ——\n // 两个退出按钮都会先把这个号从库里删掉,所以库永远攒不到第二个账号。\n const addAccountBtn = el('button', 'dsw-btn', '登录新账号(添加)') as HTMLButtonElement\n const exportBtn = el('button', 'dsw-btn ghost', '导出备份…') as HTMLButtonElement\n const importBtn = el('button', 'dsw-btn ghost', '导入备份…') as HTMLButtonElement\n // 「校验全部」:对库里每个账号做一次**只读探活**(零额度)—— 自动探活 30 分钟才一次,\n // 而\"我刚在浏览器里动过这个号,它现在到底还行不行\"是随时会冒出来的问题。\n // 以前只能等,或者切过去试(那要发一次生成请求、烧额度)。\n //\n // ⚠️ 命名避开「刷新状态」:那个名字已经被「当前账号」卡片上的按钮占了\n //(它刷的是 `/status`,只看当前那个号)—— 两个同名按钮在同一个面板里会互相误导。\n const refreshAccountsBtn = el('button', 'dsw-btn ghost', '校验全部') as HTMLButtonElement\n refreshAccountsBtn.title = '对每个账号做一次只读校验(零额度):刷新登录态、补上账号名、清掉已恢复的失败标记'\n const newGroupBtn = el('button', 'dsw-btn ghost', '新建分组') as HTMLButtonElement\n newGroupBtn.title = '给账号分类,只影响列表的显示方式 —— 不参与切号、也不影响会话复用与清理'\n accountsIOPanel.append(addAccountBtn, exportBtn, importBtn, refreshAccountsBtn, newGroupBtn)\n accountsCard.append(accountsIOPanel)\n // 以前这里是个\"要导入的备份文件路径\"输入框 —— 让人手打路径本来就别扭。\n // 现在两个按钮都弹**系统对话框**(另存为 / 打开),位置和文件名由用户自己选。\n // 实现见 src/file-picker.ts(宿主是 utility 进程,拿不到 Electron 的 dialog,\n // 只能由渲染进程用 Chromium 自己的能力做)。\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 「登录新账号」会先清掉上次的浏览器登录态(库里已有的账号不受影响),登录后新账号只入库、不切换当前账号 ——' +\n '加完在列表里点「切换」即可使用。某个账号标着「需要重新登录」时,用它自己那行上的按钮修 ——' +\n '那条路不清浏览器登录态,能复用就直接复用。导出/导入会弹系统对话框,自己选位置和文件。',\n ),\n )\n const accountsMsg = createMsgNode()\n accountsCard.append(accountsMsg.node)\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '⚠️ 导出的备份文件就是可完整登录的凭证(等同于账号本身)—— 别分享、别提交到仓库。',\n ),\n )\n acctLibraryPane.append(accountsCard)\n\n /**\n * 一个组的标题行:折叠箭头 + 组名 + 数量 + (真实组才有)改名 / 删除。\n *\n * 「未分组」是**伪组**(`groupId === null`),不给改名/删除入口 —— 它不是一个实体,\n * 只是\"没归组的账号\"的落脚处。\n */\n const sectionRow = (section: any, groups: any[]): HTMLElement => {\n const key = String(section?.key ?? '')\n const collapsed = collapsedGroups.has(key)\n const head = el('div', 'dsw-grouphead')\n const toggle = el('span', 'gtoggle', collapsed ? '▸' : '▾')\n toggle.title = collapsed ? '展开' : '折叠'\n toggle.addEventListener('click', () => {\n if (collapsedGroups.has(key)) collapsedGroups.delete(key)\n else collapsedGroups.add(key)\n persistCollapsedGroups()\n // 折叠是纯前端状态 ⇒ 直接重画,不用再打一次 /accounts(也不该走重建签名)\n redrawAccounts()\n })\n head.append(toggle, el('span', 'gname', String(section?.name ?? '')))\n head.append(el('span', 'gcount', `${Array.isArray(section?.accounts) ? section.accounts.length : 0} 个`))\n head.append(el('span', 'gspacer'))\n if (section?.groupId) {\n const renameGroupBtn = el('button', 'dsw-btn ghost', '改名') as HTMLButtonElement\n renameGroupBtn.addEventListener('click', () => {\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.value = String(section.name ?? '')\n input.placeholder = '分组名'\n head.append(input)\n input.focus()\n let settled = false\n const commit = (): void => {\n if (settled) return\n settled = true\n void renameAccountGroup(String(section.groupId), input.value)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') {\n settled = true\n redrawAccounts()\n }\n })\n input.addEventListener('blur', commit)\n })\n const deleteGroupBtn = el('button', 'dsw-btn danger', '删除') as HTMLButtonElement\n deleteGroupBtn.title = '只删分组本身;组里的账号会回到「未分组」,账号与凭证都不动'\n let armed = false\n deleteGroupBtn.addEventListener('click', () => {\n if (!armed) {\n armed = true\n deleteGroupBtn.textContent = '确认删除?'\n window.setTimeout(() => {\n armed = false\n deleteGroupBtn.textContent = '删除'\n }, 4000)\n return\n }\n void deleteAccountGroup(String(section.groupId))\n })\n head.append(renameGroupBtn, deleteGroupBtn)\n }\n return head\n }\n\n const renderAccounts = (data: any): void => {\n const items: any[] = Array.isArray(data?.accounts) ? data.accounts : []\n const groups: any[] = Array.isArray(data?.groups) ? data.groups : []\n const sections: any[] = Array.isArray(data?.sections) ? data.sections : []\n accountsBadge.textContent = `${items.length} 个`\n accountsBadge.className = `dsw-badge ${items.length ? 'on' : 'off'}`\n accountsEmpty.hidden = items.length > 0\n accountsList.textContent = ''\n // 兜底:万一拿到的是没有 sections 的载荷(老宿主 / 异常响应),退回单区平铺 ——\n // 宁可少个分组标题,也不能什么都看不到。\n if (!sections.length) {\n for (const item of items) accountsList.append(accountRow(item, groups, false))\n return\n }\n for (const section of sections) {\n accountsList.append(sectionRow(section, groups))\n if (collapsedGroups.has(String(section?.key ?? ''))) continue\n for (const item of Array.isArray(section?.accounts) ? section.accounts : []) {\n accountsList.append(accountRow(item, groups, true))\n }\n }\n }\n\n /**\n * 「重新登录这个账号」:给凭证失效(探活失败)的账号用。\n *\n * 与「登录新账号」的唯一区别是**不清浏览器登录态**:后者要先清干净才能加到别的号,\n * 而修同一个号正相反 —— 留着才可能一打开就复用上。落库仍走添加模式(只入库、不切换),\n * 所以修好它也不会顶掉你正在用的账号;若它本来就是当前账号,那正是你要的效果。\n */\n const reloginAccount = (id: string, title: string): void => {\n void (async () => {\n // 这里刻意不写死\"会复用登录态\" —— 宿主在「这条账号已被标记失效」时会走清理路径\n // (0.1.75),该说什么由它随响应返回(下面用 prep.hint),免得界面与实际行为对不上。\n accountsMsg.textContent = `正在为「${title}」打开登录窗口……`\n try {\n const prep = await api('/login/relogin', { method: 'POST', body: JSON.stringify({ id }) })\n if (prep?.ok === false) {\n accountsMsg.textContent = `准备失败:${prep?.error ?? '未知原因'}`\n return\n }\n boostUntil = Date.now() + 300_000\n accountsMsg.textContent = String(\n prep?.hint ??\n '登录窗口已打开:如果浏览器里还留着这个账号的登录态会立刻复用,否则在里面重新登录一次……',\n )\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n accountsMsg.textContent = `打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用「手动粘贴 Token」)`\n return\n }\n accountsMsg.textContent = result?.relogin\n ? `「${title}」的凭证已原地更新(同一条记录、当前账号未变),旧的失败标记也清掉了。`\n : result?.added\n ? `「${title}」已加入账号库 —— 当前使用的账号没有改变。`\n : '已捕获并保存凭证。'\n await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `重新登录失败:${error?.message ?? error}`\n } finally {\n await refresh(false).catch(() => undefined)\n }\n })()\n }\n\n const accountRow = (item: any, groups: any[] = [], indent = false): HTMLElement => {\n const row = el('li', `dsw-account${item.isActive ? ' active' : ''}${indent ? ' grouped' : ''}`)\n const main = el('div', 'dsw-account-main')\n const title = el('div', 'dsw-account-title')\n title.append(el('span', undefined, item.title || item.id))\n if (item.isActive) title.append(el('span', 'dsw-badge on', '✅ 当前'))\n if (item.unverified) title.append(el('span', 'dsw-badge off', '❔ 未校验'))\n const limited = item.limit && Number.isFinite(item.limit.untilMs) && item.limit.untilMs > Date.now()\n if (limited) {\n title.append(el('span', 'dsw-badge off', `⏳ 受限至 ${shortTime(item.limit.untilMs)}`))\n }\n // 徽章从「校验失败」改成**明确的行动指令**:原来只写\"失败\",用户不知道该干嘛,\n // 也看不出这号还能不能用。探活失败基本只有一种可能 —— 凭证失效,要重新登录。\n if (item.lastVerifyError) title.append(el('span', 'dsw-badge err', '❌ 需要重新登录'))\n main.append(title)\n\n const meta: string[] = []\n if (item.display) meta.push(item.display)\n if (item.capturedAt) meta.push(`${shortTime(item.capturedAt)} 捕获`)\n meta.push(item.lastVerifiedAt ? `最近校验 ${relTime(item.lastVerifiedAt)}` : '尚未校验')\n const cookieMeta: any[] = Array.isArray(item.cookieMeta) ? item.cookieMeta : []\n meta.push(`cookie:${describeCookieLife(summarizeCookieLife(cookieMeta))}`)\n main.append(el('div', 'dsw-account-meta', meta.join(' · ')))\n\n // 探活失败 → 不只报状态,给一条可执行的路径。\n // 为什么这里用「重新登录」而不是让用户自己点「登录新账号」:后者会**先清掉浏览器\n // 登录态**(因为它的目标是加一个*别的*号),而修同一个号正相反 —— 浏览器里可能还\n // 留着登录态,不清就能一打开直接复用,一个密码都不用敲。见宿主 /login/relogin。\n if (item.lastVerifyError) {\n // 只留一行说明(短、留在行内);修复按钮挪到右侧动作列 ——\n // 原来它独占一整行、又长又占地方(用户反馈:放到 切换/重命名/移除 那一列去)。\n main.append(\n el(\n 'div',\n 'dsw-account-fix',\n `⚠️ ${relTime(item.lastVerifyError.at)}校验失败:${item.lastVerifyError.message}`,\n ),\n )\n }\n row.append(main)\n\n const actions = el('div', 'dsw-account-actions')\n // 失效的账号:「重登」放动作列最前面 —— 它是这行最该点的按钮。\n // 文案从「重新登录」缩成「重登」:动作列现在要放下切换/备注/归组/移除,太长会挤成两行。\n if (item.lastVerifyError) {\n const reloginBtn = el('button', 'dsw-btn dsw-preset', '重登') as HTMLButtonElement\n reloginBtn.title = '能复用浏览器里的登录态就直接复用(不必敲密码);若这条账号已被标记失效,会先清掉登录态、让你重新登录一次。捕获后原地更新这条记录,不新增、也不切换当前账号'\n reloginBtn.addEventListener('click', () => reloginAccount(item.id, item.title || item.id))\n actions.append(reloginBtn)\n }\n if (item.isActive) {\n actions.append(el('span', 'dsw-hint', '使用中'))\n } else {\n const useBtn = el('button', 'dsw-btn ghost dsw-preset', '切换') as HTMLButtonElement\n useBtn.addEventListener('click', () => void switchToAccount(item.id))\n actions.append(useBtn)\n }\n\n // 文案叫「备注」而不是「重命名」:它写的是 `label`(备注名),**不是**改显示名 ——\n // 原来的名字让人以为是重命名账号本身,结果 7 个账号的 label 一直是空的(功能没人发现)。\n const renameBtn = el('button', 'dsw-btn ghost dsw-preset', '备注') as HTMLButtonElement\n let editing = false\n renameBtn.addEventListener('click', () => {\n if (editing) return\n editing = true\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.value = item.label || ''\n input.placeholder = '备注名,如「工作号」'\n main.append(input)\n input.focus()\n let settled = false\n const commit = (): void => {\n if (settled) return\n settled = true\n void saveAccountLabel(item.id, input.value)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') {\n settled = true\n void loadAccounts()\n }\n })\n input.addEventListener('blur', commit)\n })\n actions.append(renameBtn)\n\n // 归组用**下拉**而不是拖拽:这套面板是手写 DOM,拖拽的命中判定、与轮询重建的冲突\n // 都太容易出坑;下拉最少点击,而且天然幂等(\"设成某个值\"而不是\"挪到某个位置\")。\n const groupSel = el('select', 'dsw-groupsel') as HTMLSelectElement\n groupSel.title = '放进某个分组(只影响显示方式,不影响切号与会话复用)'\n groupSel.append(new Option('未分组', ''))\n for (const group of groups) groupSel.append(new Option(String(group?.name ?? ''), String(group?.id ?? '')))\n groupSel.value = String(item.groupId ?? '')\n groupSel.addEventListener('change', () => void assignAccountGroup(item.id, groupSel.value))\n actions.append(groupSel)\n\n // 移除要二次确认:凭证一旦删掉就找不回来了(不保留明文归档,见 accounts.ts)\n const removeBtn = el('button', 'dsw-btn danger dsw-preset', '移除') as HTMLButtonElement\n let armed = false\n removeBtn.addEventListener('click', () => {\n if (!armed) {\n armed = true\n removeBtn.textContent = '确认移除?'\n removeBtn.classList.add('armed')\n window.setTimeout(() => {\n armed = false\n removeBtn.textContent = '移除'\n removeBtn.classList.remove('armed')\n }, 4_000)\n return\n }\n void removeAccountById(item.id)\n })\n actions.append(removeBtn)\n row.append(actions)\n return row\n }\n\n /**\n * 渲染账号库,但**内容没变就不重建 DOM**。\n *\n * 为什么要有这道守卫:轮询会按秒级节拍重读 `/accounts`(见下面的 syncAccounts),\n * 无脑重建会把用户正在悬停/正要点的按钮换掉(重命名输入框更糟 —— 直接失焦)。\n * 签名取的是界面真正渲染的东西,见 account-sync.ts。\n */\n const applyAccounts = (data: any): void => {\n lastAccountsPayload = data\n const signature = accountsSignature(data)\n if (signature && signature === accountsSignatureCache) return\n accountsSignatureCache = signature\n renderAccounts(data)\n }\n\n /** 用最近一次载荷重画(折叠这类纯前端状态用它,不走重建签名、也不打 HTTP)。 */\n const redrawAccounts = (): void => {\n if (lastAccountsPayload) renderAccounts(lastAccountsPayload)\n }\n\n /** 显式读取(失败要说给用户听):初始化 / 切号 / 移除 / 添加 / 重新登录 / 导入之后用。 */\n const loadAccounts = async (): Promise<void> => {\n try {\n applyAccounts(await api('/accounts'))\n } catch (error: any) {\n accountsMsg.textContent = `账号库读取失败:${error?.message ?? error}`\n } finally {\n accountsSyncedAt = Date.now()\n }\n }\n\n /**\n * 后台同步账号库(静默;只在内容变了时重建列表)。\n *\n * 为什么必须有它:账号库列表**不在 `/status` 里**,登录捕获又是异步落地的\n * (CDP 那条路要等用户在浏览器里登录完;能开 Electron 窗口那条路更是\"开窗即返回\")\n * ⇒ 只靠上面几个显式调用,捕获晚一步就永远不显示,得关掉设置页再打开。\n *\n * 后台同步失败**不写进 accountsMsg** —— 那是给用户操作反馈用的,\n * 被一个后台轮询的偶发失败覆盖掉会更让人困惑(下一次成功就自然好了)。\n */\n const syncAccounts = async (): Promise<void> => {\n accountsSyncedAt = Date.now()\n try {\n applyAccounts(await api('/accounts'))\n } catch {\n // 静默:这是后台同步,失败不该覆盖用户正在看的那条消息\n }\n }\n\n /**\n * 分组动作:**只有显示方式会变,账号与凭证一律不动。**\n *\n * 每次操作后都 `loadAccounts()` 重读,而不是就地改 DOM:\n * 宿主的 `/accounts` 是唯一事实来源;重读既能拿到组的新状态,也会顺带走一遍内容签名 ——\n * 签名没变就不重建,正在输入的框不会被打断。\n */\n const groupApi = async (path: string, body: unknown, okText: string): Promise<void> => {\n try {\n const result = await api(path, { method: 'POST', body: JSON.stringify(body) })\n if (result?.ok === false) {\n accountsMsg.textContent = `分组操作失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = okText\n } catch (error: any) {\n accountsMsg.textContent = `分组操作失败:${error?.message ?? error}`\n } finally {\n await loadAccounts()\n }\n }\n\n const assignAccountGroup = (id: string, groupId: string): Promise<void> =>\n groupApi(\n '/accounts/group/assign',\n { id, groupId },\n groupId ? '已归组。' : '已移出分组(回到「未分组」)。',\n )\n\n const renameAccountGroup = (id: string, name: string): Promise<void> =>\n groupApi('/accounts/group/rename', { id, name }, `分组已改名为「${name.trim()}」。`)\n\n const deleteAccountGroup = (id: string): Promise<void> =>\n groupApi('/accounts/group/delete', { id }, '分组已删除;组里的账号回到「未分组」(账号本身没动)。')\n\n /** 「新建分组」的内联输入框(与「备注」同款交互:Enter 提交、Esc 取消、失焦也提交)。 */\n let newGroupInput: HTMLInputElement | null = null\n const promptNewGroup = (): void => {\n if (newGroupInput && newGroupInput.isConnected) {\n newGroupInput.focus()\n return\n }\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.placeholder = '分组名,如「工作号」'\n accountsIOPanel.append(input)\n input.focus()\n newGroupInput = input\n let settled = false\n const dismiss = (): void => {\n settled = true\n newGroupInput = null\n input.remove()\n }\n const commit = (): void => {\n if (settled) return\n const name = input.value\n dismiss()\n if (!name.trim()) return\n void groupApi('/accounts/group/create', { name }, `已创建分组「${name.trim()}」。`)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') dismiss()\n })\n input.addEventListener('blur', commit)\n }\n\n /**\n * 手动刷新账号状态:只读探活,零额度。\n *\n * 按钮会**禁用并改文案** —— 库里账号是串行校验的,7 个要十几秒;不置灰的话\n * 用户会以为没反应而反复点(宿主侧也有互斥,但界面先挡住更好)。\n */\n const refreshAccountStates = async (): Promise<void> => {\n refreshAccountsBtn.disabled = true\n const original = refreshAccountsBtn.textContent\n refreshAccountsBtn.textContent = '校验中…'\n accountsMsg.textContent = '正在逐个校验账号(只读、零额度,串行进行,请稍候)……'\n try {\n const result = await api('/accounts/refresh', { method: 'POST', body: '{}' })\n if (result?.ok === false) {\n accountsMsg.textContent = `刷新失败:${result?.error ?? '未知原因'}`\n return\n }\n const failed = Number(result?.failed ?? 0)\n accountsMsg.textContent =\n failed > 0\n ? `已校验 ${result?.checked ?? 0} 个账号:${result?.passed ?? 0} 个正常、${failed} 个需要重登(列表里已标出)。`\n : `已校验 ${result?.checked ?? 0} 个账号:全部正常。`\n } catch (error: any) {\n accountsMsg.textContent = `刷新失败:${error?.message ?? error}`\n } finally {\n refreshAccountsBtn.disabled = false\n refreshAccountsBtn.textContent = original\n // 探活会回写记录(补账号名、清失败标记)⇒ 让它立刻出现在列表里\n await refresh(false).catch(() => undefined)\n }\n }\n\n refreshAccountsBtn.addEventListener('click', () => void refreshAccountStates())\n newGroupBtn.addEventListener('click', () => promptNewGroup())\n\n const switchToAccount = async (id: string): Promise<void> => {\n accountsMsg.textContent = '切换中……'\n try {\n const result = await api('/accounts/switch', { method: 'POST', body: JSON.stringify({ id }) })\n if (!result?.ok) {\n accountsMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = '已切换(下一次请求生效)。正在刷新登录状态……'\n await loadAccounts()\n await refresh(false)\n accountsMsg.textContent = '已切换(下一次请求生效)'\n } catch (error: any) {\n accountsMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n const removeAccountById = async (id: string): Promise<void> => {\n try {\n const result = await api('/accounts/remove', { method: 'POST', body: JSON.stringify({ id }) })\n if (!result?.ok) {\n accountsMsg.textContent = `移除失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = '已移除该账号(凭证已删除)'\n await loadAccounts()\n await refresh(true)\n } catch (error: any) {\n accountsMsg.textContent = `移除失败:${error?.message ?? error}`\n }\n }\n\n const saveAccountLabel = async (id: string, label: string): Promise<void> => {\n try {\n await api('/accounts/rename', { method: 'POST', body: JSON.stringify({ id, label }) })\n } catch {}\n await loadAccounts()\n }\n\n addAccountBtn.addEventListener('click', () => {\n void (async () => {\n addAccountBtn.disabled = true\n accountsMsg.textContent = '正在准备登录窗口(会清掉上次的浏览器登录态,不影响账号库里的账号)……'\n try {\n const prep = await api('/login/add', { method: 'POST', body: '{}' })\n if (prep?.ok === false) {\n accountsMsg.textContent = `准备失败:${prep?.error ?? '未知原因'}`\n return\n }\n // 登录窗口是\"打开后等用户操作\"的,这里会一直等到捕获到凭证或超时;\n // 期间保持轮询更密一点,万一捕获是异步落地的也能及时刷出来。\n boostUntil = Date.now() + 300_000\n accountsMsg.textContent = '登录窗口已打开:请在窗口里登录另一个账号(不要登当前这个,否则只是刷新凭证)……'\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n accountsMsg.textContent = `打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用「手动粘贴 Token」)`\n return\n }\n if (result?.added) {\n accountsMsg.textContent = result.created\n ? '已把新账号加入账号库 —— 当前账号没有改动,点列表里的「切换」才会用它。'\n : '这个账号本来就在库里(凭证已更新)—— 当前账号未改动。'\n } else {\n accountsMsg.textContent = '已捕获并保存凭证。'\n }\n await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `添加账号失败:${error?.message ?? error}`\n } finally {\n addAccountBtn.disabled = false\n await refresh(false).catch(() => undefined)\n }\n })()\n })\n\n exportBtn.addEventListener('click', () => {\n void (async () => {\n exportBtn.disabled = true\n accountsMsg.textContent = '导出中……'\n try {\n // 先弹系统「另存为」,内容在用户选完位置后再取(顺序见 file-picker.ts 的注释)。\n const outcome = await saveWithPicker(suggestedExportName(), async () => {\n const data = await api('/accounts/export-json', { method: 'POST', body: '{}' })\n if (!data?.ok) throw new Error(data?.error ?? '读取备份内容失败')\n const { ok: _ok, ...backup } = data\n return JSON.stringify(backup, null, 2)\n })\n\n if (outcome.kind === 'saved') {\n accountsMsg.textContent = `已保存:${outcome.name}(含明文凭证,请妥善保管)`\n } else if (outcome.kind === 'cancelled') {\n accountsMsg.textContent = '已取消导出。'\n } else {\n // 系统「另存为」不可用 → 回退到宿主写插件目录(功能不因此失效,只是不能选位置)\n const result = await api('/accounts/export', { method: 'POST', body: '{}' })\n const why = outcome.kind === 'unsupported' ? '当前环境不支持系统另存为' : `系统另存为失败(${outcome.reason})`\n accountsMsg.textContent = result?.ok\n ? `${why},已改为保存到插件目录:${result.path}`\n : `导出失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n accountsMsg.textContent = `导出失败:${error?.message ?? error}`\n } finally {\n exportBtn.disabled = false\n }\n })()\n })\n\n importBtn.addEventListener('click', () => {\n void (async () => {\n importBtn.disabled = true\n accountsMsg.textContent = '请选择备份文件……'\n try {\n const file = await pickJsonFile()\n if (!file) {\n accountsMsg.textContent = '已取消导入。'\n return\n }\n accountsMsg.textContent = `正在读取 ${file.name}……`\n const source = await readImportSource(file)\n if (source.kind === 'unreadable') {\n accountsMsg.textContent = `读取失败:${source.name} 不是有效的 JSON 备份(${source.reason})`\n return\n }\n // 拿到真实路径时只把**路径**交给宿主(宿主自己读文件,凭证不进 HTTP);\n // 拿不到才退回传内容 —— 兜底,免得宿主没注入路径桥时功能整体失效。\n const body = source.kind === 'path' ? { path: source.path } : { payload: source.payload }\n const result = await api('/accounts/import', { method: 'POST', body: JSON.stringify(body) })\n accountsMsg.textContent = result?.ok\n ? `导入完成(${source.name}):新增 ${result.imported} / 更新 ${result.updated} / 跳过 ${result.skipped}`\n : `导入失败:${result?.error ?? '未知原因'}`\n if (result?.ok) await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `导入失败:${error?.message ?? error}`\n } finally {\n importBtn.disabled = false\n }\n })()\n })\n\n // ── 手动 token 卡 ──\n const tokenCard = el('div', 'dsw-card')\n tokenCard.append(el('div', 'dsw-cardhead', '手动粘贴 Token(可选路径)'))\n tokenCard.append(\n el(\n 'p',\n 'dsw-hint',\n '在浏览器打开 chat.deepseek.com 并登录 → F12 控制台执行下面一行 → 把结果粘贴到输入框:',\n ),\n )\n const snippet = el('code', 'dsw-code', \"JSON.parse(localStorage.getItem('userToken')).value\")\n tokenCard.append(snippet)\n tokenCard.append(\n el(\n 'p',\n 'dsw-hint',\n '(新版网页端 token 存在 {\"value\": …} 包装里;若上面那行报错,改成 localStorage.getItem(\\'userToken\\') 直接复制整串,插件会自动解包)',\n ),\n )\n const tokenInput = el('textarea', 'dsw-area') as HTMLTextAreaElement\n tokenInput.placeholder = '粘贴 token(包装 JSON 或裸 token 都行;可选:下一行粘贴 Cookie,格式 name=value; name2=value2)'\n tokenCard.append(tokenInput)\n const tokenActions = el('div', 'dsw-row')\n tokenActions.style.marginTop = '8px'\n const tokenBtn = el('button', 'dsw-btn', '保存并验证') as HTMLButtonElement\n tokenActions.append(tokenBtn)\n tokenCard.append(tokenActions)\n acctLibraryPane.append(tokenCard)\n\n // ── 测试卡 ──\n const testCard = el('div', 'dsw-card')\n testCard.append(el('div', 'dsw-cardhead', '连通性测试'))\n testCard.append(el('p', 'dsw-hint', '直接经适配器发一次最小请求(会消耗一点网页端额度):'))\n const testActions = el('div', 'dsw-row')\n testActions.style.marginTop = '6px'\n const modelSelect = el('select', 'dsw-input') as HTMLSelectElement\n modelSelect.style.maxWidth = '220px'\n const testBtn = el('button', 'dsw-btn', '发送测试') as HTMLButtonElement\n testActions.append(modelSelect, testBtn)\n testCard.append(testActions)\n const testOut = el('div', 'dsw-msg')\n testOut.style.display = 'none'\n testCard.append(testOut)\n\n // ── 模型卡 ──\n const modelsCard = el('div', 'dsw-card')\n modelsCard.append(el('div', 'dsw-cardhead', '可用模型(网页免费)'))\n const modelsList = el('ul', 'dsw-models')\n modelsCard.append(modelsList)\n const modelsHint = el('p', 'dsw-hint', '')\n modelsCard.append(modelsHint)\n modelPane.append(modelsCard, testCard)\n\n // (操作反馈条与 showMessage 已提到页头、标签栏之上)\n\n let loggedIn = false\n let electron = false\n let windowOpen = false\n /** 上次同步账号库的时刻(0 = 还没读过;初始化那次 refresh 之前会先读一次)。 */\n let accountsSyncedAt = 0\n /** 上次渲染用的内容签名(见 account-sync.ts)。 */\n let accountsSignatureCache = ''\n /**\n * 最近一次 `/accounts` 载荷。\n *\n * 折叠是**纯前端状态**(不进账号库、也不该进重建签名),但切换折叠要重画列表 ——\n * 所以留一份载荷,让\"折叠\"能直接触发一次局部重绘,而不必多打一次 HTTP。\n */\n let lastAccountsPayload: any = null\n\n const COLLAPSE_KEY = 'dsw-accounts-collapsed-groups'\n\n /** 折叠的组 key。读一次就常驻,避免每行都去碰 localStorage。 */\n const collapsedGroups = new Set<string>((() => {\n try {\n const raw = window.localStorage.getItem(COLLAPSE_KEY)\n const parsed = raw ? JSON.parse(raw) : []\n return Array.isArray(parsed) ? parsed.map((item: unknown) => String(item)) : []\n } catch {\n // 隐私模式 / 存储被禁:折叠状态退化成\"本次会话不折叠\",不该让面板挂掉\n return []\n }\n })())\n\n const persistCollapsedGroups = (): void => {\n try {\n window.localStorage.setItem(COLLAPSE_KEY, JSON.stringify([...collapsedGroups]))\n } catch {\n /* 存不下就算了 —— 折叠只是显示偏好 */\n }\n }\n let probeIntervalMs = 0\n\n const renderKv = (rows: [string, string][]): void => {\n statusKv.textContent = ''\n for (const [key, value] of rows) {\n statusKv.append(el('div', 'k', key), el('div', undefined, value))\n }\n }\n\n /**\n * 上下文页的状态渲染。卡片在下面才建,这里用\"先声明后赋值\"的方式接上 ——\n * 直接引用下面的 const 会在时序上踩 TDZ(applyStatus 早于卡片构造)。\n */\n let renderContextStatus: ((mode: string, chain: any) => void) | undefined\n\n const applyStatus = (status: StatusPayload): void => {\n loggedIn = !!status.auth?.loggedIn\n electron = !!status.electron\n windowOpen = !!status.loginWindowOpen\n // 上下文投喂的实际状态(模式 + 链是否真的在跑)跟着状态轮询刷新。\n // ⚠️ 这两个字段在 /status 的 **config** 里 —— 0.1.62 写成了顶层,等于这段从来没生效\n // (面板不重开就永远停在打开那一刻的段数),0.1.63 修正。\n const cfg = status.config as { contextMode?: string; contextChain?: unknown } | undefined\n if (cfg?.contextMode) {\n renderContextStatus?.(String(cfg.contextMode), cfg.contextChain)\n }\n\n badge.textContent = loggedIn ? (status.auth.unverified ? '❔ 已捕获(未校验)' : '✅ 已登录') : '⚪ 未登录'\n badge.className = `dsw-badge ${loggedIn ? (status.auth.unverified ? 'off' : 'on') : 'off'}`\n if (loggedIn && !status.auth.unverified) {\n const valid = status.validation\n if (valid && !valid.ok) {\n badge.textContent = '❌ 登录态校验失败'\n badge.className = 'dsw-badge err'\n }\n }\n\n const rows: [string, string][] = []\n activeLimitUntilMs = Number.isFinite(status.auth?.limitUntilMs) ? Number(status.auth?.limitUntilMs) : null\n paintLimit()\n probeIntervalMs = Number(status.config?.probeIntervalMs ?? 0)\n renderVersion({ current: status.config?.version, probeIntervalMs })\n renderPaths(status.paths)\n rows.push(['适配器注册', (status.registeredProviders ?? []).includes(status.provider) ? `✅ ${status.provider} 已注册到 llm` : `⚠️ 未在 llm 中找到 ${status.provider}`])\n if (loggedIn) {\n rows.push(['账号', status.auth.display || '(未获取到账号信息)'])\n rows.push(['捕获时间', status.auth.capturedAt ? new Date(status.auth.capturedAt).toLocaleString() : '未知'])\n // 凭证来源与完整度:以前这里对缺失项写「未捕获(可能仍可用)」,读起来像风险提示,\n // 实际含义只是「你走的是手动粘贴 token 那条路」。实测(2026-09-11):\n // 只有 Bearer token、没有 cookie / x-hif-* 时,校验、PoW、真实生成全部通过 ——\n // 所以这里如实说明「缺什么」以及「已证实不影响使用」,而不是留一句模糊的警告。\n const manualTokenMode = !status.auth.hasCookie && !status.auth.hasFingerprint\n if (manualTokenMode) {\n rows.push(['凭证来源', '手动粘贴 token(实测:仅凭 Bearer token 即可完成校验/求解/生成)'])\n } else {\n rows.push(['凭证来源', '浏览器登录捕获(token + cookie + 指纹头,登录态通常更耐久)'])\n }\n rows.push([\n 'Cookie',\n status.auth.hasCookie\n ? '✅ 已捕获'\n : '未捕获 —— 手动 token 模式本就没有(已验证不影响请求;若日后频繁遇到 AUTH/40003,改用「浏览器登录」)',\n ])\n if (status.auth.hasCookie) {\n // 把\"登录态到底还能撑多久\"从完全不可观察变成至少能看一半。\n // ⚠️ 措辞必须诚实:实测真正鉴权用的是 token(只发 token 不带 cookie 能通过,\n // 只发 cookie 不带 token 直接被拒 40002),所以这里只是**浏览器侧的上界**,\n // 不能让人读成\"到这天就掉线\"。\n rows.push([\n 'Cookie 过期',\n `${describeCookieLife(status.auth.cookieLife)}(会话级 = 浏览器关掉就没了;这是浏览器侧的上界,不是登录态寿命)`,\n ])\n }\n rows.push(['指纹头', status.auth.hasFingerprint ? '✅ 已捕获(x-hif-* / x-client-*)' : '未捕获 —— 同上,已实测可用'])\n rows.push(['PoW WASM', status.auth.wasmHost || '默认地址'])\n rows.push(['token 长度', `${status.auth.tokenLength ?? 0} 字符`])\n if (status.validation) rows.push(['服务端校验', status.validation.ok ? '通过' : `失败:${status.validation.error ?? ''}`])\n\n // 主动探活(后台定时做的,与上面这次\"按需校验\"是两件事):\n // 目的就是**在任务跑到一半之前**发现登录态失效。\n const verifiedAt = status.auth?.lastVerifiedAt\n const verifyError = status.auth?.lastVerifyError\n if (verifyError) {\n rows.push([\n '登录态探活',\n `❌ ${relTime(verifyError.at)}失败:${verifyError.message}(可能已过期,建议重新登录)`,\n ])\n } else if (verifiedAt) {\n rows.push(['登录态探活', `✅ ${relTime(verifiedAt)}通过(后台定时校验,零额度)`])\n }\n // 让用户能确认「防风控」到底生效成什么样(值来自配置,改配置后重启生效)\n const interval = status.config?.minRequestIntervalMs\n if (interval !== undefined) {\n rows.push([\n '请求节流',\n status.config?.allowConcurrent\n ? `⚠️ 允许并发 · 间隔 ${interval}ms(并发生成有账号级限制风险,不建议)`\n : `串行(一次只发一条)· 间隔 ${interval}ms`,\n ])\n }\n } else {\n // 未登录时说明「能用哪条路登录」(不要笼统写成「非 Electron 环境」:\n // 2026-09-11 起宿主是 utility 进程,但真实浏览器登录是可用的)\n const available = status.loginCapability\n rows.push([\n '登录方式',\n available\n ? available.canOpenWindow\n ? '插件自开窗口(Electron 主进程,带指纹伪装)'\n : available.browser\n ? `用真实浏览器登录(${available.browser} + 调试协议,自动读取凭证)`\n : '未找到 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 token'\n : electron\n ? '可开浏览器窗口'\n : '请用「用我的默认浏览器登录」+ 手动粘贴 token',\n ])\n }\n if (status.lastLoginResult) {\n rows.push(['最近结果', `${status.lastLoginResult.message} · ${new Date(status.lastLoginResult.at).toLocaleTimeString()}`])\n }\n if (status.loginWindowOpen && status.loginProgress?.captured) {\n const captured = status.loginProgress.captured\n rows.push([\n '捕获进度',\n `token ${captured.token ? '✓' : '…'} / cookie ${captured.cookie ? '✓' : '…'} / 指纹 ${captured.fingerprint ? '✓' : '…'}`,\n ])\n }\n if (status.loginProgress?.lastError && status.loginWindowOpen) {\n rows.push(['窗口提示', status.loginProgress.lastError])\n }\n if (status.fingerprint && status.fingerprint.stripped.length > 0) {\n rows.push(['指纹清理', `已剔除 ${status.fingerprint.stripped.length} 个 Electron 头:${status.fingerprint.stripped.join(', ')}`])\n } else if (status.loginWindowOpen) {\n rows.push(['指纹清理', '窗口已打开(尚未命中需要清理的头)'])\n }\n if (status.fingerprint?.pageUa) {\n const bad = /electron/i.test(status.fingerprint.pageUa)\n rows.push(['页面看到 UA', `${bad ? '⚠️ 仍含 Electron:' : '✅ '}${status.fingerprint.pageUa}`])\n }\n if (status.fingerprint?.pageBrands?.length) {\n const dirty = status.fingerprint.pageBrands.filter((b) => /electron|dsh/i.test(b))\n rows.push(['页面品牌', `${dirty.length ? `⚠️ ${dirty.join(', ')}` : '✅ '}${status.fingerprint.pageBrands.join(', ')}`])\n }\n if (status.fingerprint?.pageWebdriver !== undefined) {\n rows.push(['webdriver', status.fingerprint.pageWebdriver ? '⚠️ true(自动化痕迹)' : '✅ false'])\n }\n // ⚠️ 0.1.82:这一段必须在 renderKv 之前 —— 此前 `renderKv(rows)` 在它上面,\n // 「宿主进程」这一行 push 进去时表格已经画完了 ⇒ **那行永远不显示**,\n // 而\"窗口打不开\"时它恰恰是唯一能说明原因的证据。\n // 登录能力自检(2026-09-11 事故:DSH 把插件宿主挪到 utility 进程后,窗口 API 没了)\n const capability = status.loginCapability\n if (capability) {\n const mode = capability.canOpenWindow\n ? '可开 Electron 窗口'\n : capability.browser\n ? `无窗口 API(${capability.processType} 进程)→ 用真实浏览器`\n : `无窗口 API(${capability.processType} 进程)且未找到 Edge/Chrome`\n rows.push(['宿主进程', `${capability.processType} · ${mode}`])\n }\n renderKv(rows)\n\n if (!browserBtn.dataset.busy) browserBtn.disabled = false\n if (capability) {\n browserBtn.textContent = capability.canOpenWindow ? '浏览器窗口登录' : capability.browser ? `用 ${capability.browser} 登录` : '浏览器窗口登录'\n browserBtn.disabled = !capability.canOpenWindow && !capability.browser\n browserBtn.title = capability.canOpenWindow\n ? '插件自己开窗口(带指纹伪装)'\n : capability.browser\n ? `拉起真实的 ${capability.browser}(独立 profile)完成登录,插件通过调试协议读取登录态`\n : '既不能开窗口也没找到 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 Token'\n } else {\n browserBtn.disabled = !electron\n browserBtn.textContent = windowOpen ? '登录窗口已打开' : '浏览器窗口登录'\n browserBtn.title = electron ? '' : '当前宿主无法开窗:请用「用我的默认浏览器登录」+ 手动粘贴 Token'\n }\n\n // 账号卡:显示当前账号 + 退出按钮可用性\n accountLine.textContent = ''\n if (loggedIn) {\n accountLine.append(el('div', 'k', '账号'), el('div', undefined, status.auth.display || '(未获取到账号信息)'))\n accountLine.append(\n el('div', 'k', '登录时间'),\n el('div', undefined, status.auth.capturedAt ? new Date(status.auth.capturedAt).toLocaleString() : '未知'),\n )\n } else {\n accountLine.append(el('div', 'k', '状态'), el('div', undefined, '未登录(没有可退出的账号)'))\n }\n logoutBtn.disabled = !loggedIn\n switchBtn.disabled = !loggedIn || !electron\n switchBtn.title = electron ? '' : '当前不是 Electron 桌面端:请先「退出当前账号」,再手动粘贴另一个账号的 token'\n\n // 模型下拉\n if (modelSelect.options.length !== status.models.length) {\n modelSelect.textContent = ''\n for (const model of status.models) {\n const option = document.createElement('option')\n option.value = model.id\n option.textContent = model.name\n modelSelect.append(option)\n }\n }\n // 模型列表\n modelsList.textContent = ''\n for (const model of status.models) {\n const item = el('li')\n item.append(el('div', 'name', model.name))\n const ctxLabel = model.contextWindow >= 1_048_576\n ? `${(model.contextWindow / 1_048_576).toFixed(0)}M`\n : `${Math.round(model.contextWindow / 1024)}K`\n item.append(el('div', 'id', `${model.id} · thinking ${model.thinking ? '开' : '关'} · 上下文 ${ctxLabel} token(标称)`))\n item.append(el('div', 'id', model.description))\n modelsList.append(item)\n }\n modelsHint.textContent =\n `两条是同一个「快速模式」的思考开关两档预设(也可在模型选择器的推理强度里切换)。` +\n `图片输入直接可用(走网页端文件上传通道)。每次调用会新建并删除临时会话;` +\n `prompt 字符上限 ${status.config?.maxPromptChars ?? 0}(服务端硬上限 2621440 字符,` +\n `另附件 token 预算 890880 —— 后者常被误读成「上下文窗口」)。`\n }\n\n const refresh = async (light = true): Promise<void> => {\n try {\n const status = await api(`/status${light ? '?light=1' : ''}`)\n if (disposed) return\n applyStatus(status)\n } catch (error: any) {\n showMessage(`状态读取失败:${error?.message ?? error}`, 'err')\n }\n }\n\n // ── 请求节流卡:并发开关 + 间隔区间 + 会话清理 ──\n // 这三项直接决定会不会被账号级限流(实测双窗口并发 6 分钟内被限制 1 天),\n // 所以不该只藏在配置文件里 —— 界面上直接可调,改完即时生效并落盘。\n const gateCard = el('div', 'dsw-card')\n gateCard.append(el('div', 'dsw-cardhead', '请求节流(防风控)'))\n\n const rangeInput = (): HTMLInputElement => {\n const input = el('input', 'dsw-range') as HTMLInputElement\n input.type = 'range'\n input.min = '0'\n input.max = '30000'\n input.step = '500'\n return input\n }\n\n const concRow = el('div', 'dsw-gate-row')\n const concLabel = el('label', 'dsw-switch')\n const concInput = el('input') as HTMLInputElement\n concInput.type = 'checkbox'\n const concTrack = el('span', 'dsw-switch-track')\n concLabel.append(concInput, concTrack, el('span', undefined, '允许并发(同一账号同时发两条)'))\n concRow.append(concLabel)\n gateCard.append(concRow)\n\n const minRow = el('div', 'dsw-gate-row')\n minRow.append(el('span', 'dsw-gate-label', '间隔下限'))\n const minRange = rangeInput()\n const minValue = el('span', 'dsw-gate-value', '—')\n minRow.append(minRange, minValue)\n gateCard.append(minRow)\n\n const maxRow = el('div', 'dsw-gate-row')\n maxRow.append(el('span', 'dsw-gate-label', '间隔上限'))\n const maxRange = rangeInput()\n const maxValue = el('span', 'dsw-gate-value', '—')\n maxRow.append(maxRange, maxValue)\n gateCard.append(maxRow)\n\n const intervalHint = el('p', 'dsw-hint', '')\n gateCard.append(intervalHint)\n\n const presetRow = el('div', 'dsw-gate-row')\n gateCard.append(presetRow)\n\n // ── prompt 上限(token 体量的总阀门)──────────────────────────\n // 为什么它比\"间隔\"更值得调:网页 API 无状态 → **每一轮都要把整段转写重发**,\n // 转写越长、单次请求越贵。实测同一个会话里单次输入估算从 9.7k token 涨到 293k\n // (180 轮累计约 2900 万),随后账号被限流。间隔只影响\"多久发一次\",这个才影响\"每次发多少\"。\n const promptRow = el('div', 'dsw-gate-row')\n promptRow.append(el('span', 'dsw-gate-label', 'prompt 上限'))\n const promptRange = el('input', 'dsw-range') as HTMLInputElement\n promptRange.type = 'range'\n promptRange.setAttribute('aria-label', 'prompt 字符上限')\n promptRange.title = '每次请求最多发送多少字符(含工具目录与全部历史)'\n const promptValue = el('span', 'dsw-gate-value', '—')\n promptRow.append(promptRange, promptValue)\n gateCard.append(promptRow)\n const promptHint = el('p', 'dsw-hint', '')\n gateCard.append(promptHint)\n\n /** 字符数 → 「万字符」。界面上出现一长串 0 没人看得出差别。 */\n const fmtChars = (n: number): string => `${Number.isFinite(n) ? (n / 10_000).toFixed(n % 10_000 === 0 ? 0 : 1) : '—'} 万字符`\n\n const paintPromptCap = (): void => {\n const chars = Number(promptRange.value)\n promptValue.textContent = fmtChars(chars)\n const level = chars >= 1_200_000 ? '偏高' : chars >= 600_000 ? '中等' : '保守'\n promptHint.textContent =\n `每次请求最多发送 ${fmtChars(chars)}(${level})。` +\n '网页端是无状态的,每一轮都会把整段对话历史重新发一遍:' +\n '上限越大,模型越不容易「忘事」,但单次消耗的 token 也越多。' +\n '长任务建议调小,或拆成多个会话(新会话从零开始,单次最省)。'\n }\n // 把「底部那两个数字到底怎么来的」直接写在界面上:\n // 否则「缓存命中 0%」会被当成\"真的没命中\",而它其实只是我们没有上报。\n const tokenNote = el('p', 'dsw-hint', '')\n tokenNote.textContent =\n '💡 说明:DSH 底部的 token 数目前是按字符估算的(网页端只回一个「本消息累计 token」,' +\n '我们还没接进来);「缓存命中 0%」是因为网页端不提供缓存信息、我们也就没有上报 —— ' +\n '不代表真的没命中。'\n gateCard.append(tokenNote)\n\n promptRange.addEventListener('input', paintPromptCap)\n promptRange.addEventListener('change', () => {\n paintPromptCap()\n void saveGate({ maxPromptChars: Number(promptRange.value) })\n })\n\n // ── 图片数量上限(另一个请求级阀门)──────────────────────────\n // 为什么需要:图片是**请求级**的(一次请求用一个 ref_file_ids 带一批),网页端对这一批\n // 有数量上限 —— 群友实测 40 张通过 / 52 张被拒。超了会以 biz_code 10 拒收**整轮**,\n // 而图还留在历史里 ⇒ **该会话此后每一轮都失败**,用户只能丢掉全部上下文。\n // 所以按时间只带最近的 N 张,更早的略过。\n const imgRow = el('div', 'dsw-gate-row')\n imgRow.append(el('span', 'dsw-gate-label', '图片上限'))\n const imgRange = el('input', 'dsw-range') as HTMLInputElement\n imgRange.type = 'range'\n imgRange.setAttribute('aria-label', '单次请求的图片数量上限')\n imgRange.title = '一次请求最多带多少张历史图片(0 = 不限制)'\n const imgValue = el('span', 'dsw-gate-value', '—')\n imgRow.append(imgRange, imgValue)\n gateCard.append(imgRow)\n const imgHint = el('p', 'dsw-hint', '')\n gateCard.append(imgHint)\n\n const paintImageCap = (): void => {\n const count = Number(imgRange.value)\n imgValue.textContent = count === 0 ? '不限制' : `${count} 张`\n imgHint.textContent =\n count === 0\n ? '不限制(不推荐):历史里不同图片数超过 40 张左右后,网页端会以 code 10 拒绝整轮,且该会话此后每轮都失败。'\n : `一次请求最多带最近的 ${count} 张图片,更早的在本次请求里略过 —— 这是正常的长度控制,不是错误。` +\n '网页端能引用的图片数上限实测在 40~52 之间,默认值留了足够余量。'\n }\n imgRange.addEventListener('input', paintImageCap)\n imgRange.addEventListener('change', () => {\n paintImageCap()\n void saveGate({ maxRefImages: Number(imgRange.value) })\n })\n\n const cleanupRow = el('div', 'dsw-gate-row')\n cleanupRow.append(el('span', 'dsw-gate-label', '会话清理'))\n const cleanupBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['immediate', '立即'],\n ['deferred', '延迟(推荐)'],\n ['keep', '不删'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveGate({ sessionCleanup: key }))\n cleanupBtns[key] = btn\n cleanupRow.append(btn)\n }\n gateCard.append(cleanupRow)\n const cleanupHint = el('p', 'dsw-hint', '')\n gateCard.append(cleanupHint)\n\n // ── 会话清理的三个区间(只在「延迟」模式显示)──────────────────────\n // 为什么这三个参数也要\"上下限 + 随机\":它们原来都是**死值** —— 攒到第 8 个就动手、\n // 正好等 90 秒、逐个删除时请求连发。固定值方差≈0,本身就是机器特征;真人不会这么精确。\n // 所以每个参数给一对上下限,实际取值在区间内**随机抽**(阈值/等待每轮重抽,删除间隔每次重抽)。\n const cleanupRanges = el('div')\n cleanupRanges.style.display = 'none'\n gateCard.append(cleanupRanges)\n\n /** 一对「上下限」滑块:一行两个 range + 右侧合成值。拖动中只更新数字,松手才提交。 */\n const rangePair = (\n label: string,\n bounds: { min: number; max: number },\n step: string,\n format: (lo: number, hi: number) => string,\n commit: (lo: number, hi: number) => void,\n ): { row: HTMLElement; set: (lo: number, hi: number) => void } => {\n const row = el('div', 'dsw-gate-row')\n row.append(el('span', 'dsw-gate-label', label))\n const lo = el('input', 'dsw-range') as HTMLInputElement\n const hi = el('input', 'dsw-range') as HTMLInputElement\n for (const [input, which] of [[lo, '下限'], [hi, '上限']] as const) {\n input.type = 'range'\n input.min = String(bounds.min)\n input.max = String(bounds.max)\n input.step = step\n input.setAttribute('aria-label', `${label}${which}`)\n input.title = `${label}${which}`\n }\n const value = el('span', 'dsw-gate-pair-value', '—')\n const paint = (): void => {\n value.textContent = format(Number(lo.value), Number(hi.value))\n }\n const onInput = (): void => {\n // 两个滑块允许被拖反 —— 当场收紧成「下限 <= 上限」。\n // 后端也会纠正,但界面先纠更直观:总比\"拖完看着不对、过一会儿自己跳回去\"好。\n if (Number(lo.value) > Number(hi.value)) {\n const swap = lo.value\n lo.value = hi.value\n hi.value = swap\n }\n paint()\n }\n const onCommit = (): void => commit(Number(lo.value), Number(hi.value))\n lo.addEventListener('input', onInput)\n hi.addEventListener('input', onInput)\n lo.addEventListener('change', onCommit)\n hi.addEventListener('change', onCommit)\n const pair = el('div', 'dsw-range-pair')\n pair.append(lo, hi)\n row.append(pair, value)\n return {\n row,\n set: (lo2, hi2) => {\n lo.value = String(lo2)\n hi.value = String(hi2)\n paint()\n },\n }\n }\n\n const batchPair = rangePair(\n '攒够数量',\n { min: 1, max: 50 },\n '1',\n (lo, hi) => `${lo}~${hi} 个`,\n (lo, hi) => void saveGate({ cleanupBatch: { min: lo, max: hi } }),\n )\n const delayPair = rangePair(\n '最长等待',\n { min: 5, max: 600 },\n '5',\n (lo, hi) => `${lo}~${hi} 秒`,\n (lo, hi) => void saveGate({ cleanupDelayMs: { min: Math.round(lo * 1000), max: Math.round(hi * 1000) } }),\n )\n const gapPair = rangePair(\n '删除间隔',\n { min: 0, max: 60 },\n '0.1',\n (lo, hi) => (hi <= 0 ? '不等待' : `${lo.toFixed(1)}~${hi.toFixed(1)} 秒`),\n (lo, hi) => void saveGate({ cleanupGapMs: { min: Math.round(lo * 1000), max: Math.round(hi * 1000) } }),\n )\n cleanupRanges.append(batchPair.row, delayPair.row, gapPair.row)\n cleanupRanges.append(\n el(\n 'p',\n 'dsw-hint',\n '上面三个都是「下限 ~ 上限」:实际取值每次在区间内随机抽 —— 攒批阈值与最长等待每轮清理重抽、' +\n '删除间隔每删一个重抽。这样\"什么时候动手删\"就没有固定规律了。',\n ),\n )\n cleanupRanges.append(\n el(\n 'p',\n 'dsw-hint',\n '「删除间隔」只在逐个删除时起作用:优先走一个请求批量删;若服务端不接受批量删(会自动退化为逐个删),' +\n '相邻两个删除请求之间就按这个间隔停一下 —— 避免\"一下子连发几十个删除请求\"。',\n ),\n )\n\n gateCard.append(\n el(\n 'p',\n 'dsw-hint',\n '并发默认关闭:DSH 的会话标题生成会与主回答同时发往同一账号,网页端同一账号同时只能生成一条,' +\n '实测双窗口并发不到 6 分钟即触发账号级限制(1 天)。',\n ),\n )\n const gateMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n gateCard.append(gateMsg)\n gatePane.append(gateCard)\n\n // ── 调用台账:请求密度 + 失败分类 ──────────────────────────────\n // 节流\"到底有没有生效\"不能靠感觉 —— 看两个数:相邻对话的间隔分布(最短间隔说明有没有连环请求)、\n // 以及失败分类(限流 / 账号被限制 / 鉴权 / 网络各占多少)。\n const ledgerCard = el('div', 'dsw-card')\n const ledgerHead = el('div', 'dsw-cardhead')\n ledgerHead.append(el('span', 'name', '调用台账'))\n const ledgerBadge = el('span', 'dsw-badge off', '近 24 小时')\n ledgerHead.append(ledgerBadge)\n ledgerCard.append(ledgerHead)\n ledgerCard.append(\n el('p', 'dsw-hint', '本地记录每次调用的结果(不含任何对话内容与凭证)。用来判断节流是否真的在起作用。'),\n )\n const ledgerKv = el('div', 'dsw-kv')\n ledgerCard.append(ledgerKv)\n const ledgerSparkBox = el('div')\n ledgerCard.append(ledgerSparkBox)\n const ledgerRow = el('div', 'dsw-row')\n const ledgerRefreshBtn = el('button', 'dsw-btn ghost', '刷新台账') as HTMLButtonElement\n ledgerRow.append(ledgerRefreshBtn)\n ledgerCard.append(ledgerRow)\n const ledgerMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n ledgerCard.append(ledgerMsg)\n gatePane.append(ledgerCard)\n\n /** 24 根小柱:每小时调用量。高度按最大值归一,失败多的时段用红色。 */\n const sparkSvg = (hourly: number[], failedHours: Set<number>): string => {\n const values = hourly.length ? hourly : [0]\n const max = Math.max(1, ...values)\n const barW = 100 / values.length\n let out = '<svg class=\"dsw-spark\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">'\n values.forEach((value, index) => {\n const height = (value / max) * 100\n // 基础填充由 .dsw-spark rect 给;只有\"有失败\"的时段才额外挂 class(CSS 里 .bad 覆盖填充色)\n const cls = failedHours.has(index) ? ' class=\"bad\"' : ''\n out += `<rect${cls} x=\"${(index * barW).toFixed(2)}\" y=\"${(100 - height).toFixed(2)}\" width=\"${(barW * 0.7).toFixed(2)}\" height=\"${Math.max(height, value > 0 ? 3 : 0.6).toFixed(2)}\" rx=\"1\"></rect>`\n })\n out += '</svg>'\n return out\n }\n\n const fmtMs = (ms: number): string => (ms >= 60_000 ? `${(ms / 60_000).toFixed(1)} 分` : `${(ms / 1000).toFixed(1)} 秒`)\n\n const renderLedger = (data: any): void => {\n const calls = Number(data?.calls ?? 0)\n ledgerBadge.textContent = `近 ${data?.hours ?? 24} 小时`\n ledgerBadge.className = `dsw-badge ${calls > 0 ? 'on' : 'off'}`\n ledgerKv.textContent = ''\n ledgerSparkBox.textContent = ''\n if (calls === 0) {\n ledgerMsg.textContent = '还没有记录 —— 跑一次对话后回来看。'\n return\n }\n const push = (key: string, value: string): void => {\n ledgerKv.append(el('div', 'k', key), el('div', undefined, value))\n }\n push('调用次数', `${calls}(成功 ${data.succeeded ?? 0} / 失败 ${data.failed ?? 0})`)\n const gaps = data.gaps\n push(\n '相邻对话间隔',\n gaps\n ? `中位 ${fmtMs(gaps.p50)} · p90 ${fmtMs(gaps.p90)} · 最短 ${fmtMs(gaps.min)}(样本 ${gaps.samples})`\n : '样本不足',\n )\n if (gaps && gaps.min < 1_500) {\n ledgerMsg.textContent = `⚠️ 出现过 ${fmtMs(gaps.min)} 的极短间隔 —— 检查一下节流是否被关掉了。`\n }\n const failures = data.failures ?? {}\n push(\n '失败分类',\n Object.keys(failures).length ? Object.entries(failures).map(([key, count]) => `${key} ${count}`).join(' · ') : '无',\n )\n push('台账占用', `${data.footprint?.files ?? 0} 个文件 · ${Math.round((data.footprint?.bytes ?? 0) / 1024)} KB`)\n // F3(0.2.0):按账号用量 —— 多账号场景\"哪个号在烧钱/在被限\"一目了然。\n const byAccount: any[] = Array.isArray(data.byAccount) ? data.byAccount : []\n if (byAccount.length > 0) {\n const nameOf = (id: string): string => {\n const items: any[] = Array.isArray(lastAccountsPayload?.accounts) ? lastAccountsPayload.accounts : []\n const hit = items.find((item) => item.id === id)\n return hit ? String(hit.label || hit.display || hit.id) : id\n }\n push(\n '按账号',\n byAccount\n .sort((a, b) => (Number(b?.calls) || 0) - (Number(a?.calls) || 0))\n .map((row) => `${nameOf(row.accountId)} ${row.calls} 次(失败 ${row.failed})`)\n .join(' · '),\n )\n }\n const hourly: number[] = Array.isArray(data.hourly) ? data.hourly : []\n // 0.1.82:失败时段此前恒传空集 ⇒ `.bad` 是死样式、\"标红\"从未生效\n const failedHours = new Set<number>()\n const hourlyFailed: number[] = Array.isArray(data.hourlyFailed) ? data.hourlyFailed : []\n hourlyFailed.forEach((count, index) => {\n if (count > 0) failedHours.add(index)\n })\n ledgerSparkBox.innerHTML = sparkSvg(hourly, failedHours)\n ledgerSparkBox.append(el('p', 'dsw-hint', `每小时调用量(最近 1 小时在最右,峰值 ${Math.max(0, ...hourly)} 次)`))\n }\n\n const loadLedger = async (): Promise<void> => {\n try {\n renderLedger(await api('/ledger?hours=24'))\n } catch (error: any) {\n ledgerMsg.textContent = `台账读取失败:${error?.message ?? error}`\n }\n }\n ledgerRefreshBtn.addEventListener('click', () => void loadLedger())\n\n // ── 传输层卡:请求从哪个网络栈出去(决定 TLS/HTTP2 指纹像不像浏览器)──\n // 实测:Node fetch 的 JA4 是 `t13d…h1`(不走 HTTP/2、cipher 数量差 3 倍多、不带 GREASE);\n // 换成 Chromium 网络栈后 cipher 列表哈希与 Chrome **逐字节一致**。所以默认走 Chromium。\n // 但要留开关:Chromium 会跟随**系统代理**(Node 完全无视代理),梯子关着时可能反而连不上。\n const transportCard = el('div', 'dsw-card')\n transportCard.append(el('div', 'dsw-cardhead', '传输层(指纹)'))\n\n const transportRow = el('div', 'dsw-gate-row')\n transportRow.append(el('span', 'dsw-gate-label', '请求从哪出去'))\n const transportBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['chromium', 'Chromium 网络栈(推荐)'],\n ['node', 'Node'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveTransport(key))\n transportBtns[key] = btn\n transportRow.append(btn)\n }\n transportCard.append(transportRow)\n\n const transportHint = el('p', 'dsw-hint', '')\n transportCard.append(transportHint)\n const transportProxyHint = el('p', 'dsw-hint', '')\n transportCard.append(transportProxyHint)\n\n const testRow = el('div', 'dsw-gate-row')\n const transportTestBtn = el('button', 'dsw-btn ghost', '测试传输层(零额度)') as HTMLButtonElement\n testRow.append(transportTestBtn)\n transportCard.append(testRow)\n\n const transportTestOut = el('div', 'dsw-msg', '')\n transportTestOut.style.display = 'none'\n transportCard.append(transportTestOut)\n\n const transportMsg = createMsgNode()\n transportCard.append(transportMsg.node)\n transportPane.append(transportCard)\n\n // ── 上下文页:每轮发全量 prompt,还是只发增量 + 父消息链 ────────────\n // 背景(2026-09-14,读参考实现 + 抓真实帧得出):插件一直发 parent_message_id:null,\n // 每条消息都是会话里的根消息、没有父链,服务端回溯上下文到空 —— 所以历史只能每轮重发。\n // 浏览器不是这么干的(只有会话第一条 parent 是 null),链式投喂就是照它做:\n // 只发增量、把上一条回答挂成父消息。代价是工具协议只存在于链首那条消息里,\n // 一旦服务端把早期上下文丢掉,模型可能不按约定格式发工具调用 —— 所以默认仍是全量。\n const contextCard = el('div', 'dsw-card')\n contextCard.append(el('div', 'dsw-cardhead', '上下文投喂方式'))\n const contextRow = el('div', 'dsw-gate-row')\n contextRow.append(el('span', 'dsw-gate-label', '每轮发什么'))\n const contextBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['full', '每轮全量(默认)'],\n ['chained', '链式投喂(只发增量)'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveContextMode(key))\n contextBtns[key] = btn\n contextRow.append(btn)\n }\n contextCard.append(contextRow)\n const contextStatus = el('p', 'dsw-hint', '')\n contextCard.append(contextStatus)\n const contextHintText = el('p', 'dsw-hint', '')\n contextCard.append(contextHintText)\n const contextMsg = createMsgNode()\n contextCard.append(contextMsg.node)\n contextPane.append(contextCard)\n\n renderContextStatus = (rawMode: string, chain: any): void => {\n const mode = rawMode === 'chained' ? 'chained' : 'full'\n for (const key of Object.keys(contextBtns)) {\n contextBtns[key].classList.toggle('active', key === mode)\n }\n if (chain) {\n contextStatus.textContent =\n `链式投喂正在跑:网页端会话 ${String(chain.sessionId ?? '').slice(0, 8)},` +\n `链上已发 ${chain.turns} 段,父消息 ${chain.parentId}`\n } else {\n contextStatus.textContent =\n mode === 'chained'\n ? '链式投喂:下一条消息会重新起链(当前没有可续的链,或还没开始用)'\n : '当前:每轮重发全量 prompt(和以前完全一致)'\n }\n }\n\n const applyContextCard = (info: any): void => {\n renderContextStatus?.(String(info?.mode ?? 'full'), info?.chain)\n const hint = String(info?.hint ?? '')\n // 设置文件路径单独一行 —— 想手工改配置的人需要它\n const path = String(info?.settingsPath ?? '')\n contextHintText.textContent = path ? `${hint}\\n配置文件:${path}` : hint\n }\n\n const saveContextMode = async (mode: 'full' | 'chained'): Promise<void> => {\n contextMsg.textContent = '切换中……'\n try {\n const result = await api('/context-mode', { method: 'POST', body: JSON.stringify({ mode }) })\n if (result?.ok) {\n applyContextCard(result)\n contextMsg.textContent =\n `已切到${result.mode === 'chained' ? '链式投喂' : '每轮全量'},即时生效` +\n (result.persisted === false\n ? '(未能写入配置,重启后会回到上次保存的值)'\n : '(已写入配置,重启后仍生效)')\n } else {\n contextMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n contextMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n // ── 关于页:版本与更新 / 数据位置 / 风险提示 ────────────────────\n // 单独一个标签而不是塞进别的页:这三块都是\"偶尔看一眼\"的信息,\n // 混进日常操作的页里只会稀释注意力。\n const aboutCard = el('div', 'dsw-card')\n aboutCard.append(el('div', 'dsw-cardhead', '版本与更新'))\n const versionKv = el('div', 'dsw-kv')\n aboutCard.append(versionKv)\n const updateRow = el('div', 'dsw-row')\n updateRow.style.marginTop = '8px'\n const updateBtn = el('button', 'dsw-btn ghost', '检查更新') as HTMLButtonElement\n updateRow.append(updateBtn)\n aboutCard.append(updateRow)\n const updateMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n aboutCard.append(updateMsg)\n aboutCard.append(\n el('p', 'dsw-hint', '插件装不了包,所以这里只做\"检查 + 给链接\"。GitHub 在国内可能连不上,检查失败是正常的。'),\n )\n aboutPane.append(aboutCard)\n\n const renderVersion = (info: any): void => {\n versionKv.textContent = ''\n versionKv.append(el('div', 'k', '当前版本'), el('div', undefined, info?.current || '未知'))\n versionKv.append(\n el('div', 'k', '登录态探活'),\n el(\n 'div',\n undefined,\n info?.probeIntervalMs > 0\n ? `每 ${Math.round(info.probeIntervalMs / 60_000)} 分钟一次(只读、零额度)`\n : '已关闭',\n ),\n )\n }\n\n updateBtn.addEventListener('click', () => {\n void (async () => {\n updateBtn.disabled = true\n updateMsg.textContent = '正在检查……'\n try {\n const result = await api('/update-check', { method: 'POST', body: '{}' })\n if (!result?.ok) {\n updateMsg.textContent = `检查失败:${result?.error ?? '未知原因'}`\n return\n }\n const current = result.current\n renderVersion({ current, probeIntervalMs })\n if (result.hasUpdate) {\n updateMsg.textContent = `发现新版本 ${result.latest}(当前 ${current})${\n result.publishedAt ? ` · ${shortTime(result.publishedAt)} 发布` : ''\n }${result.url ? ` · ${result.url}` : ''}`\n } else {\n updateMsg.textContent = `已是最新版本(${current})。`\n }\n } catch (error: any) {\n updateMsg.textContent = `检查失败:${error?.message ?? error}`\n } finally {\n updateBtn.disabled = false\n }\n })()\n })\n\n const pathsCard = el('div', 'dsw-card')\n pathsCard.append(el('div', 'dsw-cardhead', '数据位置'))\n pathsCard.append(\n el('p', 'dsw-hint', '插件的本地状态都在 DSH 主目录下,不进通用配置面(避免凭证混进 settings/credentials)。'),\n )\n const pathsKv = el('div', 'dsw-kv')\n pathsCard.append(pathsKv)\n pathsCard.append(\n el('p', 'dsw-hint', '⚠️ 账号库里每个文件都是可完整登录的凭证;导出的备份同样是明文 —— 账号库卡片里的提示请当真。'),\n )\n aboutPane.append(pathsCard)\n\n const renderPaths = (paths: any): void => {\n pathsKv.textContent = ''\n const rows: [string, string][] = [\n ['账号库', paths?.accounts || '(未知)'],\n ['账号索引', paths?.webLogin ? `${paths.webLogin}/accounts.json` : '(未知)'],\n ['调用台账', paths?.ledger || '(未知)'],\n ['导出备份', paths?.webLogin ? `${paths.webLogin}/exports/` : '(未知)'],\n ]\n for (const [key, value] of rows) {\n pathsKv.append(el('div', 'k', key), el('div', 'dsw-path', value))\n }\n }\n\n const riskCard = el('div', 'dsw-card')\n riskCard.append(el('div', 'dsw-cardhead', '为什么没有「自动换号」'))\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 账号库支持一键手动切换,但刻意不做自动轮换(检测到限流就自动换一个号继续发)。原因不是保守:',\n ),\n )\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '参考项目切的是「CLI 下次启动用哪个账号」——服务端看不到;而我们每次对话都实时发请求。' +\n '真人不会在几分钟内换一个账号接着发消息,自动换号是极强的机器行为特征,' +\n '与本插件在传输层指纹、随机间隔、会话清理上「降低机器可识别性」的努力直接冲突。',\n ),\n )\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '另外:同一服务商会把多账号关联起来(同设备 / 同 IP / 同指纹 / 相近的行为模式)。' +\n '一旦被判定为同一人的多开小号,处置通常比单账号超频更重,而且可能波及全部关联账号。' +\n '所以账号库的目标是「在自己的多个正常账号之间切换更省事」,不是「靠轮换把限流绕过去」。',\n ),\n )\n aboutPane.append(riskCard)\n\n const applyTransportCard = (info: any): void => {\n if (!info) return\n const requested = String(info.requested ?? info.effective ?? 'chromium')\n const effective = String(info.effective ?? requested)\n for (const key of Object.keys(transportBtns)) {\n transportBtns[key].classList.toggle('active', key === requested)\n }\n // 环境不支持时把 Chromium 选项禁掉,别让人选了没反应\n const available = info.chromiumAvailable !== false\n transportBtns.chromium.disabled = !available\n transportBtns.chromium.title = available\n ? ''\n : '本环境拿不到 electron.net.fetch(只有 Electron 的 utility 进程里有)'\n\n const parts = [\n effective === 'chromium' ? '实际生效:Chromium 网络栈' : '实际生效:Node fetch',\n ]\n if (info.degraded) parts.push('配置要求 Chromium,但本环境不支持,已降级为 Node')\n if (effective === 'chromium') parts.push('cipher 列表哈希与 Chrome 一致、ALPN 走 h2')\n transportHint.textContent = parts.join(' · ')\n transportProxyHint.textContent = String(info.hint ?? '')\n }\n\n const saveTransport = async (kind: 'chromium' | 'node'): Promise<void> => {\n transportMsg.textContent = '切换中……'\n try {\n const result = await api('/transport', { method: 'POST', body: JSON.stringify({ transport: kind }) })\n if (result?.ok) {\n applyTransportCard(result)\n transportMsg.textContent =\n `已切换到 ${result.effective === 'chromium' ? 'Chromium 网络栈' : 'Node fetch'},即时生效` +\n (result.persisted === false\n ? '(未能写入配置,重启后会回到上次保存的值)'\n : '(已写入配置,重启后仍生效)')\n } else {\n transportMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n transportMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n transportTestBtn.addEventListener('click', () => {\n void (async () => {\n transportTestBtn.disabled = true\n transportTestOut.style.display = ''\n transportTestOut.textContent = '正在探测(指纹 / 流式 / 鉴权)……'\n try {\n const result = await api('/diagnostics/net-fetch', {\n method: 'POST',\n body: JSON.stringify({ mode: 'probe' }),\n })\n const lines: string[] = []\n for (const step of result?.results ?? []) {\n const title = String(step.step ?? '')\n if (title.startsWith('①')) {\n lines.push(`① 指纹 ja4=${step.ja4 ?? '-'} HTTP=${step.http_version ?? '-'}`)\n } else if (title.startsWith('②')) {\n lines.push(\n `② 流式 ${step.ok ? '支持' : '不支持'} 分片=${step.chunks ?? 0} abort=${step.abortedEarly ? '生效' : '未生效'}`,\n )\n } else if (title.startsWith('③')) {\n lines.push(`③ 鉴权 HTTP ${step.status ?? '-'} ${step.ok ? '通过' : (step.error ?? '未通过')}`)\n }\n }\n transportTestOut.textContent = lines.length ? lines.join('\\n') : JSON.stringify(result)\n } catch (error: any) {\n transportTestOut.textContent = `探测失败:${error?.message ?? error}`\n } finally {\n transportTestBtn.disabled = false\n }\n })()\n })\n\n void (async () => {\n try {\n applyTransportCard(await api('/transport'))\n } catch {\n transportMsg.textContent = '传输层设置读取失败(宿主未响应)'\n }\n try {\n applyContextCard(await api('/context-mode'))\n } catch {\n contextMsg.textContent = '上下文设置读取失败(宿主未响应)'\n }\n })()\n\n // 账号库首次加载(此时 refresh 已经定义好了)\n void loadAccounts()\n // 台账首次加载\n void loadLedger()\n // 版本 / 数据位置来自 /status,这里主动拉一次让它别等到第一次轮询\n void refresh(true)\n\n /** 渲染宿主返回的节流设置(含可选档位与清理策略)。 */\n const applyGate = (g: any): void => {\n if (!g) return\n concInput.checked = !!g.allowConcurrent\n\n const cap = String(g.maxIntervalMs ?? 30000)\n minRange.max = cap\n maxRange.max = cap\n const lo = Number(g.minRequestIntervalMs ?? 2000)\n const hi = Number(g.maxRequestIntervalMs ?? lo)\n minRange.value = String(lo)\n maxRange.value = String(hi)\n minValue.textContent = `${lo}ms`\n maxValue.textContent = `${hi}ms`\n intervalHint.textContent =\n lo === hi\n ? `固定间隔 ${lo}ms(上下限相等)。建议拉开成区间 —— 固定值方差≈0,是明显的「定时器特征」。`\n : `实际等待在 ${lo}~${hi}ms 之间随机取值(均值约 ${Math.round((lo + hi) / 2)}ms)。`\n\n presetRow.textContent = ''\n presetRow.append(el('span', 'dsw-gate-label', '快捷'))\n const presets: { min: number; max: number }[] =\n g.presets ?? [{ min: 1_500, max: 2_500 }, { min: 2_000, max: 4_000 }, { min: 5_000, max: 9_000 }]\n for (const preset of presets) {\n const isDefault =\n preset.min === (g.defaultMinIntervalMs ?? 2_000) && preset.max === (g.defaultMaxIntervalMs ?? 4_000)\n const btn = el(\n 'button',\n 'dsw-btn ghost dsw-preset',\n `${preset.min}~${preset.max}ms${isDefault ? '(推荐)' : ''}`,\n ) as HTMLButtonElement\n btn.addEventListener('click', () =>\n void saveGate({ minRequestIntervalMs: preset.min, maxRequestIntervalMs: preset.max }),\n )\n if (isDefault && g.minRequestIntervalMs === preset.min && g.maxRequestIntervalMs === preset.max) {\n btn.classList.add('active')\n }\n presetRow.append(btn)\n }\n\n const capBounds = g.maxPromptCharsBounds ?? { min: 120_000, max: 1_500_000 }\n promptRange.min = String(capBounds.min)\n promptRange.max = String(capBounds.max)\n promptRange.step = '20000'\n promptRange.value = String(g.maxPromptChars ?? g.maxPromptCharsDefault ?? capBounds.max)\n paintPromptCap()\n\n const imgBounds = g.maxRefImagesBounds ?? { min: 0, max: 100 }\n imgRange.min = String(imgBounds.min)\n imgRange.max = String(imgBounds.max)\n imgRange.step = '1'\n imgRange.value = String(g.maxRefImages ?? g.maxRefImagesDefault ?? 24)\n paintImageCap()\n\n const mode = g.cleanup?.mode ?? 'deferred'\n for (const key of Object.keys(cleanupBtns)) {\n cleanupBtns[key].classList.toggle('active', key === mode)\n }\n // 三个区间只在「延迟」模式下有意义(立即 = 固定 1.5s/每次一个;不删 = 不清理)\n const showRanges = mode === 'deferred'\n cleanupRanges.style.display = showRanges ? '' : 'none'\n if (showRanges) {\n const batchRange = g.cleanup?.batchRange ?? g.cleanupDefaults?.batch\n if (batchRange) batchPair.set(batchRange.min, batchRange.max)\n const delayRange = g.cleanup?.delayRange ?? g.cleanupDefaults?.delayMs\n if (delayRange) delayPair.set(Math.round(delayRange.min / 1000), Math.round(delayRange.max / 1000))\n const gapRange = g.cleanup?.gapRange ?? g.cleanupDefaults?.gapMs\n if (gapRange) gapPair.set(+(gapRange.min / 1000).toFixed(1), +(gapRange.max / 1000).toFixed(1))\n }\n cleanupHint.textContent =\n mode === 'keep'\n ? '不删:请求最少,但网页端会留下临时会话记录。'\n : mode === 'immediate'\n ? '立即:调用结束后 1.5 秒删掉(老行为,每轮多一个删除请求)。'\n : `延迟:这一轮攒够 ${g.cleanup?.batchSize ?? 8} 个、或最多等 ` +\n `${Math.round((g.cleanup?.delayMs ?? 90_000) / 1000)} 秒就集中清理,并优先用一个请求批量删 ——` +\n '减少「每轮建一个立刻删一个」的机器特征。上面三个区间决定\"这一轮具体攒几个 / 等多久\"。'\n }\n\n const saveGate = async (patch: {\n allowConcurrent?: boolean\n minRequestIntervalMs?: number\n maxRequestIntervalMs?: number\n sessionCleanup?: string\n maxPromptChars?: number\n maxRefImages?: number\n cleanupBatch?: { min: number; max: number }\n cleanupDelayMs?: { min: number; max: number }\n cleanupGapMs?: { min: number; max: number }\n }): Promise<void> => {\n gateMsg.textContent = '保存中……'\n try {\n const result = await api('/gate', { method: 'POST', body: JSON.stringify(patch) })\n if (result?.ok) {\n applyGate(result)\n gateMsg.textContent =\n `已生效:${result.allowConcurrent ? '允许并发(不推荐)' : '串行'} · ` +\n `间隔 ${result.minRequestIntervalMs}~${result.maxRequestIntervalMs}ms · ` +\n `清理 ${result.sessionCleanup ?? result.cleanup?.mode ?? '-'}` +\n (result.cleanup?.batchRange\n ? `(阈值 ${result.cleanup.batchRange.min}~${result.cleanup.batchRange.max} 个 · ` +\n `等待 ${Math.round((result.cleanup.delayRange?.min ?? 0) / 1000)}~` +\n `${Math.round((result.cleanup.delayRange?.max ?? 0) / 1000)}s · ` +\n (result.cleanup.gapRange\n ? `删除间隔 ${(result.cleanup.gapRange.min / 1000).toFixed(1)}~${(result.cleanup.gapRange.max / 1000).toFixed(1)}s`\n : '删除间隔关') +\n ')'\n : '') +\n ` · prompt 上限 ${fmtChars(Number(result.maxPromptChars ?? 0))}` +\n (result.persisted === false ? ` ⚠️ ${result.warning ?? '未能写入配置'}` : '(已写入配置,重启后仍生效)')\n } else {\n gateMsg.textContent = `保存失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n gateMsg.textContent = `保存失败:${error?.message ?? error}`\n }\n }\n\n concInput.addEventListener('change', () => void saveGate({ allowConcurrent: concInput.checked }))\n // 拖动中只更新数字,松手(change)才提交,避免一路发请求\n minRange.addEventListener('input', () => {\n minValue.textContent = `${minRange.value}ms`\n })\n maxRange.addEventListener('input', () => {\n maxValue.textContent = `${maxRange.value}ms`\n })\n minRange.addEventListener('change', () => void saveGate({ minRequestIntervalMs: Number(minRange.value) }))\n maxRange.addEventListener('change', () => void saveGate({ maxRequestIntervalMs: Number(maxRange.value) }))\n\n void (async () => {\n try {\n applyGate(await api('/gate'))\n } catch {\n gateMsg.textContent = '节流设置读取失败(宿主未响应)'\n }\n })()\n\n let boostUntil = 0\n\n recoverBtn.addEventListener('click', () => {\n void (async () => {\n recoverBtn.disabled = true\n showMessage('正在从已登录窗口分区读取凭证……(复用上次登录态,不需要重新登录)')\n try {\n const result = await api('/login/recover', { method: 'POST', body: '{}' })\n if (result?.ok) {\n showMessage(`${result.verified ? '✅' : '⚠️'} ${result.message}`, result.verified ? 'ok' : '')\n } else {\n showMessage(`❌ ${result?.message ?? '恢复失败'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`恢复失败:${error?.message ?? error}`, 'err')\n }\n recoverBtn.disabled = false\n await refresh(false)\n })()\n })\n\n externalBtn.addEventListener('click', () => {\n void (async () => {\n externalBtn.disabled = true\n try {\n const result = await api('/login/external', { method: 'POST', body: '{}' })\n if (result?.ok) {\n showMessage(\n `已用系统默认浏览器打开 ${result.url}\\n` +\n '① 在浏览器里正常登录;②按 F12 → Console,粘贴下方「手动粘贴 Token」卡里的那行命令;' +\n '③ 把打印出来的结果粘到那张卡的输入框 → 点「保存并验证」。',\n )\n } else {\n showMessage(`打开失败:${result?.message ?? '未知原因'};请手动在浏览器访问 ${result?.url ?? 'https://chat.deepseek.com'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`打开失败:${error?.message ?? error}`, 'err')\n }\n externalBtn.disabled = false\n })()\n })\n\n browserBtn.addEventListener('click', () => {\n void (async () => {\n browserBtn.disabled = true\n // 轮询(2 秒一次)会无条件把它设回可用 ⇒ 用标记让 applyStatus 跳过重算,\n // 否则用户在等待期间再点一下就会开第二个登录窗口(并发提交凭证)。\n browserBtn.dataset.busy = '1'\n showMessage(\n '正在启动浏览器……会先清掉上次的登录状态,请在其中登录 DeepSeek(登录成功后会自动捕获,不用复制粘贴)。',\n )\n try {\n // fresh:登录前先清掉独立 profile 与登录分区里的登录态。\n // 这个按钮此前是四个登录入口里**唯一没有前置清理**的那个 —— profile 里若还留着\n // 上次那个账号,窗口一打开就是已登录,用户以为在登录、抓回来的却是旧号。\n const result = await api('/login/browser', { method: 'POST', body: JSON.stringify({ fresh: true }) })\n if (result?.mode === 'browser') {\n // 真实浏览器 + CDP:成功即已抓完 token/cookie/指纹头\n if (result.ok) {\n showMessage(`${result.verified ? '✅' : '⚠️'} ${result.message}${result.display ? `(${result.display})` : ''}`, result.verified ? 'ok' : '')\n } else {\n showMessage(`❌ ${result.message ?? `登录未完成(${result.reason ?? '未知'})`}`, 'err')\n }\n } else if (!result?.started) {\n showMessage(\n result?.reason === 'not-electron'\n ? '当前宿主进程无法开 Electron 窗口,且没找到可用的 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 Token。'\n : `打开失败:${result?.reason ?? '未知原因'}`,\n 'err',\n )\n } else {\n showMessage('登录窗口已打开,正在旁路捕获登录凭证……')\n }\n } catch (error: any) {\n showMessage(`打开登录窗口失败:${error?.message ?? error}`, 'err')\n }\n delete browserBtn.dataset.busy\n boostUntil = Date.now() + 5 * 60_000\n await refresh(false)\n })()\n })\n\n tokenBtn.addEventListener('click', () => {\n void (async () => {\n const raw = tokenInput.value.trim()\n if (!raw) {\n showMessage('请先粘贴 userToken', 'err')\n return\n }\n const [tokenLine, ...rest] = raw.split('\\n')\n const token = tokenLine.trim()\n const cookie = rest.join(' ').trim()\n tokenBtn.disabled = true\n showMessage('正在校验 token……')\n try {\n const result = await api('/login/token', { method: 'POST', body: JSON.stringify({ token, cookie }) })\n if (result?.ok) {\n if (result.error) {\n showMessage(`⚠️ ${result.error}`, '')\n } else {\n showMessage(`✅ ${result.display ? `账号 ${result.display} · ` : ''}token 校验通过,已保存`, 'ok')\n }\n tokenInput.value = ''\n } else {\n showMessage(`❌ 校验失败:${result?.error ?? '未知原因'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`保存失败:${error?.message ?? error}`, 'err')\n }\n tokenBtn.disabled = false\n await refresh(false)\n })()\n })\n\n /**\n * 二次确认:退出账号是不可逆操作(凭证 + 浏览器登录态都会被清),\n * 所以第一次点击只把按钮变成「确认退出?」,3 秒内再点一次才真的执行。\n * 不用 window.confirm(插件面板里被宿主拦截的风险,且样式不可控)。\n */\n const armConfirm = (button: HTMLButtonElement, label: string, confirmLabel: string, run: () => void): void => {\n let armed = false\n let timer: number | undefined\n const reset = (): void => {\n armed = false\n if (timer !== undefined) window.clearTimeout(timer)\n button.textContent = label\n button.classList.remove('armed')\n }\n button.textContent = label\n button.addEventListener('click', () => {\n if (!armed) {\n armed = true\n button.textContent = confirmLabel\n button.classList.add('armed')\n timer = window.setTimeout(reset, 3_000)\n return\n }\n reset()\n run()\n })\n }\n\n const doLogout = (thenLogin: boolean): void => {\n void (async () => {\n logoutBtn.disabled = true\n switchBtn.disabled = true\n try {\n await api('/logout', { method: 'POST', body: '{}' })\n // 视觉上把 token 输入框也清掉,避免误以为「还是那个账号」\n tokenInput.value = ''\n showMessage(thenLogin ? '已退出当前账号,正在打开登录窗口……' : '已退出当前账号:本地凭证与浏览器登录态都已清除。')\n await refresh(false)\n if (thenLogin) {\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n showMessage(`已退出账号;打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用手动粘贴 token)`, 'err')\n } else {\n boostUntil = Date.now() + 180_000\n showMessage('已退出账号,登录窗口已打开:请在窗口里登录其它账号,凭证会自动捕获。')\n }\n }\n } catch (error: any) {\n showMessage(`退出失败:${error?.message ?? error}`, 'err')\n } finally {\n await refresh(false)\n }\n })()\n }\n\n armConfirm(logoutBtn, '退出当前账号', '确认退出?', () => doLogout(false))\n armConfirm(switchBtn, '退出并登录其它账号', '确认退出并换号?', () => doLogout(true))\n\n testBtn.addEventListener('click', () => {\n void (async () => {\n testBtn.disabled = true\n testOut.style.display = 'block'\n testOut.className = 'dsw-msg'\n testOut.textContent = '请求中……(首次调用要解 PoW,可能需要几秒)'\n try {\n const result = await api('/test', { method: 'POST', body: JSON.stringify({ model: modelSelect.value }) })\n if (result?.ok) {\n testOut.className = 'dsw-msg ok'\n testOut.textContent = `✅ ${result.ms}ms · ${result.model}\\n${result.text || '(空响应)'}${result.reasoning ? `\\n\\n[思考] ${result.reasoning}` : ''}`\n } else {\n testOut.className = 'dsw-msg err'\n testOut.textContent = `❌ ${result?.code ? `[${result.code}] ` : ''}${result?.error ?? '失败'}`\n }\n } catch (error: any) {\n testOut.className = 'dsw-msg err'\n testOut.textContent = `❌ ${error?.message ?? error}`\n }\n testBtn.disabled = false\n })()\n })\n\n refreshBtn.addEventListener('click', () => void refresh(false))\n\n root.append(page)\n void refresh(false)\n // 倒计时独立走一个 30 秒的定时器:不产生任何请求,只把\"还剩多久\"重新算一遍。\n const countdownTimer = window.setInterval(paintLimit, 30_000)\n\n timer = window.setInterval(() => {\n const active = windowOpen || Date.now() < boostUntil\n // 账号库单独同步:它不在 /status 里,靠这一句才能\"加完账号自己出现\"。\n // 登录流程进行中 3 秒一次(捕获一落地就能看见),空闲 30 秒一次(够让探活补上的\n // 账号名 / 限制状态 / 失败标记自己出现,又不至于一直读盘)。\n if (shouldSyncAccounts(Date.now(), accountsSyncedAt, active)) void syncAccounts()\n if (active) {\n void refresh(true)\n return\n }\n // 空闲态:低频全量刷新(含服务端校验)\n if (Math.random() < 0.15) void refresh(false)\n }, 2000)\n\n return () => {\n disposed = true\n if (timer !== undefined) window.clearInterval(timer)\n window.clearInterval(countdownTimer)\n }\n }, [])\n\n return createElement('div', { ref: hostRef })\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(\n () =>\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register({ name: 'settings.section', id: 'deepseek-web-login', order: 46, label: () => 'DeepSeek 网页登录' }, () =>\n createElement(Panel),\n ),\n ),\n 'deepseek-web-login: settings page',\n )\n}\n"],"mappings":";;;;;;;;EAsCA,MAAM,YAAiB;;;;;;;;EASvB,SAAgB,kBAAkB,MAA+B,IAAS,IAAI,GAAuB;GACnG,IAAI,CAAC,MAAM,OAAO,KAAA;GAClB,IAAI;IACF,MAAM,WAAW,EAAE,2BAA2B,iBAAiB,IAAI;IACnE,OAAO,OAAO,aAAa,YAAY,SAAS,KAAK,IAAI,WAAW,KAAA;GACtE,QAAQ;IAEN;GACF;EACF;;EAGA,SAAgB,kBAAkB,IAAS,IAAI,GAAY;GACzD,OAAO,OAAO,GAAG,uBAAuB;EAC1C;;;;;;;;EASA,SAAgB,oBAAoB,sBAAY,IAAI,KAAK,GAAW;GAClE,MAAM,OAAO,UAAkB,OAAO,KAAK,CAAC,CAAC,SAAS,GAAG,GAAG;GAI5D,OAAO,qBAAqB,GAFvB,IAAI,YAAY,IAAI,IAAI,IAAI,SAAS,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,CAAC,EAAA,GAC9D,IAAI,IAAI,SAAS,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,IACtC;EACpC;;EAWA,SAAgB,eAAe,OAAyB;GACtD,OAAQ,OAAe,SAAS;EAClC;;;;;;;;;EAUA,eAAsB,eACpB,eACA,SACA,IAAS,IAAI,GACS;GACtB,IAAI,CAAC,kBAAkB,CAAC,GAAG,OAAO,EAAE,MAAM,cAAc;GAIxD,IAAI;GAGJ,IAAI;IACF,MAAM,SAAS,MAAM,EAAE,mBAAoB;KACzC;KACA,OAAO,CAAC;MAAE,aAAa;MAAW,QAAQ,EAAE,oBAAoB,CAAC,OAAO,EAAE;KAAE,CAAC;IAC/E,CAAC;IACD,MAAM,OAAO,MAAM,QAAQ;IAC3B,WAAY,MAAM,OAAO,eAAe;IACxC,MAAM,SAAS,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,mBAAmB,CAAC,CAAC;IACnE,MAAM,SAAS,MAAM;IACrB,WAAW,KAAA;IACX,OAAO;KAAE,MAAM;KAAS,MAAM,OAAO,QAAQ,SAAS,YAAY,OAAO,OAAO,OAAO,OAAO;IAAc;GAC9G,SAAS,OAAY;IACnB,IAAI,UACF,IAAI;KACF,MAAM,SAAS,MAAM,KAAK;IAC5B,QAAQ,CAAC;IAEX,IAAI,eAAe,KAAK,GAAG,OAAO,EAAE,MAAM,YAAY;IACtD,OAAO;KAAE,MAAM;KAAU,QAAQ,GAAG,OAAO,QAAQ,GAAG,GAAG,OAAO,WAAW,QAAQ,KAAK;IAAE;GAC5F;EACF;;;;;;;;EASA,SAAgB,aAAa,MAAgB,UAAqC;GAChF,OAAO,IAAI,SAAS,YAAY;IAC9B,MAAM,QAAQ,IAAI,cAAc,OAAO;IACvC,MAAM,OAAO;IACb,MAAM,SAAS;IACf,MAAM,MAAM,UAAU;IACtB,IAAI,UAAU;IAGd,MAAM,QAAQ,iBAAiB,OAAO,KAAA,CAAS,GAAG,GAAW;IAC7D,MAAM,UAAU,SAA2B;KACzC,IAAI,SAAS;KACb,UAAU;KACV,aAAa,KAAK;KAClB,MAAM,OAAO;KACb,QAAQ,IAAI;IACd;IACA,MAAM,iBAAiB,gBAAgB,OAAO,MAAM,QAAQ,EAAE,CAAC;IAE/D,MAAM,iBAAiB,gBAAgB,OAAO,KAAA,CAAS,CAAC;IACxD,IAAI,KAAK,YAAY,KAAK;IAC1B,MAAM,MAAM;GACd,CAAC;EACH;;;EAIA,MAAa,0BAA0B;;;;;;;EAevC,eAAsB,iBAAiB,MAAY,IAAS,IAAI,GAA0B;GACxF,MAAM,OAAO,KAAK,QAAQ;GAE1B,IAAI,OAAO,KAAK,SAAS,YAAY,KAAK,OAAA,SACxC,OAAO;IACL,MAAM;IACN;IACA,QAAQ,MAAM,KAAK,KAAK,KAAK,OAAO,IAAI,EAAE,YAAY,KAAK,MAAM,0BAA0B,OAAO,IAAI,EAAE;GAC1G;GAEF,MAAM,OAAO,kBAAkB,MAAM,CAAC;GACtC,IAAI,MAAM,OAAO;IAAE,MAAM;IAAQ;IAAM;GAAK;GAC5C,IAAI;IACF,OAAO;KAAE,MAAM;KAAW,SAAS,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC;KAAG;IAAK;GACzE,SAAS,OAAY;IACnB,OAAO;KAAE,MAAM;KAAc;KAAM,QAAQ,GAAG,OAAO,WAAW;IAAQ;GAC1E;EACF;;;;;;;;ECtFA,SAAgB,oBACd,OACA,MAAc,KAAK,IAAI,GACQ;GAC/B,MAAM,OAAO,SAAS,CAAC;GACvB,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;GAC9B,MAAM,aAAa,KAAK,QAAQ,SAAS,CAAC,KAAK,WAAW,OAAO,SAAS,KAAK,SAAS,CAAC;GACzF,IAAI;GACJ,KAAK,MAAM,QAAQ,YAAY;IAC7B,MAAM,YAAY,KAAK;IACvB,IAAI,CAAC,UAAU,YAAY,OAAO,WAChC,SAAS;KAAE,MAAM,KAAK;KAAM;KAAW,WAAW,YAAY,OAAO;IAAW;GAEpF;GACA,OAAO;IACL,OAAO,KAAK;IACZ,cAAc,KAAK,SAAS,WAAW;IACvC,iBAAiB,WAAW;IAC5B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC7B;EACF;;EAGA,SAAgB,kBAAkB,UAA0B;GAC1D,IAAI,YAAY,GAAG,OAAO;GAC1B,IAAI,YAAY,GAAG,OAAO,MAAM,KAAK,MAAM,QAAQ,EAAE;GAErD,OAAO,MADO,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,EAAE,CACjC,EAAE;EACrB;;;;;;;;;EAUA,SAAgB,mBACd,SACA,MAAc,KAAK,IAAI,GACf;GACR,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,QAAQ,CAAC,GAAG,QAAQ,MAAM,GAAG;GACnC,MAAM,KAAK,QAAQ,eAAe,IAAI,GAAG,QAAQ,aAAa,QAAQ,MAAM;GAC5E,IAAI,QAAQ,kBAAkB,GAAG,MAAM,KAAK,GAAG,QAAQ,gBAAgB,KAAK;GAC5E,IAAI,QAAQ,QAAQ;IAGlB,MAAM,YAAY,QAAQ,OAAO,YAAY,OAAO;IACpD,MAAM,KAAK,GAAG,QAAQ,OAAO,KAAK,GAAG,kBAAkB,QAAQ,GAAG;GACpE;GACA,OAAO,MAAM,KAAK,KAAK;EACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECtIA,MAAa,0BAA0B;;EAGvC,MAAa,wBAAwB;;;;;;;;EASrC,SAAgB,mBAAmB,KAAa,cAAsB,QAA0B;GAC9F,MAAM,MAAM,SAAS,0BAA0B;GAC/C,OAAO,MAAM,gBAAgB;EAC/B;;;;;;;;;;;;;;;;EAiBA,SAAgB,kBAAkB,SAA0B;GAC1D,MAAM,OAAQ,WAAW,CAAC;GAC1B,MAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;GACjE,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;GACjE,IAAI;IACF,OAAO,KAAK,UAAU;KAAE,UAAU,KAAK,YAAY;KAAM;KAAU;KAAQ;IAAS,CAAC;GACvF,QAAQ;IAEN,OAAO;GACT;EACF;;;;;;;;;;ECrDA,MAAa,SAAS,CAAC,OAAO;EAE9B,MAAM,MAAM;EAkDZ,eAAe,IAAI,MAAc,MAAkC;GAKjE,OAAO,OAAM,MAJU,MAAM,MAAM,MAAM;IACvC,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,GAAG;GACL,CAAC,EAAA,CACqB,KAAK;EAC7B;;;;;;;;;;;;;;;EAgBA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiKf,SAAS,UAAU,OAAwC;GACzD,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO;GAClE,MAAM,OAAO,IAAI,KAAK,KAAK;GAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;GACzC,MAAM,sBAAM,IAAI,KAAK;GACrB,MAAM,UAAU,KAAK,aAAa,MAAM,IAAI,aAAa;GACzD,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;GACrG,OAAO,UAAU,OAAO,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,KAAK,QAAQ,EAAE,GAAG;EACtE;;EAGA,SAAS,QAAQ,OAA+B;GAC9C,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,QAAQ;GACnC,IAAI,CAAC,OAAO,SAAS,EAAE,GAAG,OAAO;GACjC,MAAM,OAAO,KAAK,IAAI,IAAI;GAC1B,IAAI,OAAO,KAAQ,OAAO;GAC1B,IAAI,OAAO,MAAW,OAAO,GAAG,KAAK,MAAM,OAAO,GAAM,EAAE;GAC1D,IAAI,OAAO,OAAY,OAAO,GAAG,KAAK,MAAM,OAAO,IAAS,EAAE;GAC9D,OAAO,GAAG,KAAK,MAAM,OAAO,KAAU,EAAE;EAC1C;;EAGA,SAAS,UAAU,SAAiC;GAClD,IAAI,CAAC,WAAW,CAAC,OAAO,SAAS,OAAO,GAAG,OAAO;GAClD,MAAM,OAAO,UAAU,KAAK,IAAI;GAChC,IAAI,QAAQ,GAAG,OAAO;GACtB,MAAM,eAAe,KAAK,KAAK,OAAO,GAAM;GAC5C,MAAM,QAAQ,KAAK,MAAM,eAAe,EAAE;GAC1C,MAAM,UAAU,eAAe;GAC/B,IAAI,QAAQ,GAAG,OAAO,MAAM,MAAM,MAAM,QAAQ;GAChD,OAAO,MAAM,QAAQ;EACvB;EAEA,SAAS,GAAG,KAAa,KAAc,MAA4B;GACjE,MAAM,OAAO,SAAS,cAAc,GAAG;GACvC,IAAI,KAAK,KAAK,YAAY;GAC1B,IAAI,SAAS,KAAA,GAAW,KAAK,cAAc;GAC3C,OAAO;EACT;;EAGA,SAAS,QAAa;GACpB,MAAM,WAAA,GAAUA,MAAAA,OAAAA,CAA8B,IAAI;GAElD,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,OAAO,QAAQ;IACrB,IAAI,CAAC,MAAM;IACX,IAAI,WAAW;IACf,IAAI;IAEJ,MAAM,QAAQ,SAAS,cAAc,OAAO;IAC5C,MAAM,cAAc;IAEpB,MAAM,OAAO,GAAG,OAAO,UAAU;IACjC,MAAM,QAAQ,GAAG,MAAM,aAAa,qBAAqB;IAOzD,MAAM,MAAM,GAAG,KAAK,SAAS;IAC7B,IAAI,OAAO,sDAAsD;IACjE,IAAI,OAAO,GAAG,QAAQ,eAAe,cAAc,CAAC;IACpD,IAAI,OAAO,GAAG;IACd,KAAK,OAAO,OAAO,OAAO,GAAG;IAI7B,MAAM,UAAU,GAAG,OAAO,WAAW;IACrC,QAAQ,MAAM,UAAU;IACxB,KAAK,OAAO,OAAO;IAEnB,MAAM,eAAe,MAAc,OAA0B,OAAa;KACxE,QAAQ,cAAc;KACtB,QAAQ,YAAY,YAAY,OAAO,IAAI,SAAS;KACpD,QAAQ,MAAM,UAAU;IAC1B;IAKA,MAAM,WAAW;KAAC;KAAW;KAAS;KAAQ;KAAa;KAAW;IAAO;IAC7E,MAAM,aAAqC;KACzC,SAAS;KACT,OAAO;KACP,MAAM;KACN,WAAW;KACX,SAAS;KACT,OAAO;IACT;IACA,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,MAAM,gBAAgB,GAAG,OAAO,UAAU;IAC1C,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,QAAqC;KACzC,SAAS;KACT,OAAO;KACP,MAAM;KACN,WAAW;KACX,SAAS;KACT,OAAO;IACT;IACA,MAAM,aAAgD,CAAC;IACvD,MAAM,aAAa,QAAsB;KACvC,KAAK,MAAM,QAAQ,UAAU;MAC3B,MAAM,KAAK,SAAS;MACpB,MAAM,KAAK,CAAC,SAAS,CAAC;MACtB,WAAW,KAAK,CAAC,UAAU,OAAO,UAAU,EAAE;MAC9C,WAAW,KAAK,CAAC,aAAa,iBAAiB,OAAO,EAAE,CAAC;KAC3D;IACF;IACA,MAAM,SAAS,GAAG,OAAO,UAAU;IACnC,OAAO,aAAa,QAAQ,SAAS;IACrC,KAAK,MAAM,OAAO,UAAU;KAC1B,MAAM,MAAM,GAAG,UAAU,WAAW,WAAW,IAAI;KACnD,IAAI,OAAO;KACX,IAAI,aAAa,QAAQ,KAAK;KAC9B,IAAI,iBAAiB,eAAe,UAAU,GAAG,CAAC;KAClD,WAAW,OAAO;KAClB,OAAO,OAAO,GAAG;KACjB,MAAM,IAAI,CAAC,aAAa,QAAQ,UAAU;IAC5C;IACA,KAAK,OAAO,QAAQ,GAAG,SAAS,KAAK,QAAQ,MAAM,IAAI,CAAC;IACxD,UAAU,SAAS;IAOnB,MAAM,YAAY,CAAC,UAAU,SAAS;IACtC,MAAM,cAAsC;KAAE,QAAQ;KAAQ,SAAS;IAAM;IAC7E,MAAM,iBAAiB,GAAG,OAAO,UAAU;IAC3C,MAAM,kBAAkB,GAAG,OAAO,UAAU;IAC5C,MAAM,YAAyC;KAAE,QAAQ;KAAgB,SAAS;IAAgB;IAClG,MAAM,cAAiD,CAAC;IACxD,MAAM,iBAAiB,QAAsB;KAC3C,KAAK,MAAM,QAAQ,WAAW;MAC5B,MAAM,KAAK,SAAS;MACpB,UAAU,KAAK,CAAC,SAAS,CAAC;MAC1B,YAAY,KAAK,CAAC,UAAU,OAAO,UAAU,EAAE;MAC/C,YAAY,KAAK,CAAC,aAAa,iBAAiB,OAAO,EAAE,CAAC;KAC5D;IACF;IACA,MAAM,aAAa,GAAG,OAAO,aAAa;IAC1C,WAAW,aAAa,QAAQ,SAAS;IACzC,KAAK,MAAM,OAAO,WAAW;KAC3B,MAAM,MAAM,GAAG,UAAU,cAAc,YAAY,IAAI;KACvD,IAAI,OAAO;KACX,IAAI,aAAa,QAAQ,KAAK;KAC9B,IAAI,iBAAiB,eAAe,cAAc,GAAG,CAAC;KACtD,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;KACrB,UAAU,IAAI,CAAC,aAAa,QAAQ,UAAU;IAChD;IACA,YAAY,OAAO,YAAY,gBAAgB,eAAe;IAG9D,cAAc,QAAQ;IAGtB,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,MAAM,aAAa,GAAG,KAAK;IAC3B,WAAW,OAAO,GAAG,QAAQ,QAAQ,MAAM,CAAC;IAC5C,MAAM,QAAQ,GAAG,QAAQ,iBAAiB,OAAO;IACjD,WAAW,OAAO,KAAK;IACvB,UAAU,OAAO,UAAU;IAC3B,UAAU,OAAO,SAAS;IAE1B,MAAM,WAAW,GAAG,OAAO,QAAQ;IACnC,UAAU,OAAO,QAAQ;IAMzB,IAAI,qBAAoC;IACxC,MAAM,WAAW,GAAG,KAAK,oBAAoB;IAC7C,SAAS,MAAM,UAAU;IACzB,SAAS,MAAM,YAAY;IAC3B,UAAU,OAAO,QAAQ;IACzB,MAAM,mBAAyB;KAC7B,IAAI,CAAC,oBAAoB;MACvB,SAAS,MAAM,UAAU;MACzB;KACF;KACA,MAAM,OAAO,UAAU,kBAAkB;KACzC,SAAS,MAAM,UAAU;KACzB,SAAS,cACP,SAAS,QACL,YAAY,UAAU,kBAAkB,EAAE,MAC1C,YAAY,KAAK,GAAG,UAAU,kBAAkB,EAAE;IAC1D;IAKA,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,YAAY,OAAO,GAAG,OAAO,gBAAgB,MAAM,CAAC;IACpD,MAAM,cAAc,GAAG,OAAO,QAAQ;IACtC,YAAY,OAAO,WAAW;IAC9B,MAAM,iBAAiB,GAAG,OAAO,SAAS;IAC1C,eAAe,MAAM,YAAY;IACjC,MAAM,YAAY,GAAG,UAAU,kBAAkB,QAAQ;IACzD,MAAM,YAAY,GAAG,UAAU,iBAAiB,WAAW;IAC3D,eAAe,OAAO,WAAW,SAAS;IAC1C,YAAY,OAAO,cAAc;IACjC,MAAM,cAAc,GAClB,KACA,YACA,qFAEF;IACA,YAAY,OAAO,WAAW;IAE9B,MAAM,eAAe,GAAG,OAAO,SAAS;IACxC,aAAa,MAAM,YAAY;IAC/B,MAAM,aAAa,GAAG,UAAU,WAAW,SAAS;IACpD,MAAM,cAAc,GAAG,UAAU,iBAAiB,YAAY;IAC9D,MAAM,aAAa,GAAG,UAAU,iBAAiB,UAAU;IAC3D,MAAM,aAAa,GAAG,UAAU,iBAAiB,MAAM;IACvD,aAAa,OAAO,YAAY,aAAa,YAAY,UAAU;IACnE,UAAU,OAAO,YAAY;IAC7B,UAAU,OACR,GACE,KACA,YACA,iHAEF,CACF;IACA,UAAU,OACR,GACE,KACA,YACA,iDACF,CACF;IACA,eAAe,OAAO,WAAW,WAAW;;;;;;;;;IAiB5C,MAAM,sBAAkE;KACtE,MAAM,OAAO,GAAG,KAAK,yBAAyB,EAAE;KAChD,IAAI,OAAO;KACX,OAAO;MACL;MACA,IAAI,cAAsB;OACxB,OAAO;MACT;MACA,IAAI,YAAY,OAAe;OAC7B,OAAO,SAAS;OAChB,KAAK,cAAc;OACnB,KAAK,YAAY,iBAAiB,KAAK,IAAI,IACvC,gBACA,2BAA2B,KAAK,IAAI,IAClC,eACA;MACR;KACF;IACF;IAEA,MAAM,eAAe,GAAG,OAAO,UAAU;IACzC,MAAM,eAAe,GAAG,OAAO,cAAc;IAC7C,aAAa,OAAO,GAAG,QAAQ,QAAQ,KAAK,CAAC;IAC7C,MAAM,gBAAgB,GAAG,QAAQ,iBAAiB,GAAG;IACrD,aAAa,OAAO,aAAa;IACjC,aAAa,OAAO,YAAY;IAChC,aAAa,OACX,GACE,KACA,YACA,kDACF,CACF;IAEA,MAAM,eAAe,GAAG,MAAM,cAAc;IAC5C,aAAa,OAAO,YAAY;IAChC,MAAM,gBAAgB,GAAG,KAAK,YAAY,sCAAsC;IAChF,aAAa,OAAO,aAAa;IAEjC,MAAM,kBAAkB,GAAG,OAAO,SAAS;IAC3C,gBAAgB,MAAM,YAAY;IAGlC,MAAM,gBAAgB,GAAG,UAAU,WAAW,WAAW;IACzD,MAAM,YAAY,GAAG,UAAU,iBAAiB,OAAO;IACvD,MAAM,YAAY,GAAG,UAAU,iBAAiB,OAAO;IAOvD,MAAM,qBAAqB,GAAG,UAAU,iBAAiB,MAAM;IAC/D,mBAAmB,QAAQ;IAC3B,MAAM,cAAc,GAAG,UAAU,iBAAiB,MAAM;IACxD,YAAY,QAAQ;IACpB,gBAAgB,OAAO,eAAe,WAAW,WAAW,oBAAoB,WAAW;IAC3F,aAAa,OAAO,eAAe;IAKnC,aAAa,OACX,GACE,KACA,YACA,oJAGF,CACF;IACA,MAAM,cAAc,cAAc;IAClC,aAAa,OAAO,YAAY,IAAI;IACpC,aAAa,OACX,GACE,KACA,YACA,6CACF,CACF;IACA,gBAAgB,OAAO,YAAY;;;;;;;IAQnC,MAAM,cAAc,SAAc,WAA+B;KAC/D,MAAM,MAAM,OAAO,SAAS,OAAO,EAAE;KACrC,MAAM,YAAY,gBAAgB,IAAI,GAAG;KACzC,MAAM,OAAO,GAAG,OAAO,eAAe;KACtC,MAAM,SAAS,GAAG,QAAQ,WAAW,YAAY,MAAM,GAAG;KAC1D,OAAO,QAAQ,YAAY,OAAO;KAClC,OAAO,iBAAiB,eAAe;MACrC,IAAI,gBAAgB,IAAI,GAAG,GAAG,gBAAgB,OAAO,GAAG;WACnD,gBAAgB,IAAI,GAAG;MAC5B,uBAAuB;MAEvB,eAAe;KACjB,CAAC;KACD,KAAK,OAAO,QAAQ,GAAG,QAAQ,SAAS,OAAO,SAAS,QAAQ,EAAE,CAAC,CAAC;KACpE,KAAK,OAAO,GAAG,QAAQ,UAAU,GAAG,MAAM,QAAQ,SAAS,QAAQ,IAAI,QAAQ,SAAS,SAAS,EAAE,GAAG,CAAC;KACvG,KAAK,OAAO,GAAG,QAAQ,SAAS,CAAC;KACjC,IAAI,SAAS,SAAS;MACpB,MAAM,iBAAiB,GAAG,UAAU,iBAAiB,IAAI;MACzD,eAAe,iBAAiB,eAAe;OAC7C,MAAM,QAAQ,GAAG,SAAS,gBAAgB;OAC1C,MAAM,QAAQ,OAAO,QAAQ,QAAQ,EAAE;OACvC,MAAM,cAAc;OACpB,KAAK,OAAO,KAAK;OACjB,MAAM,MAAM;OACZ,IAAI,UAAU;OACd,MAAM,eAAqB;QACzB,IAAI,SAAS;QACb,UAAU;QACV,mBAAwB,OAAO,QAAQ,OAAO,GAAG,MAAM,KAAK;OAC9D;OACA,MAAM,iBAAiB,YAAY,UAAU;QAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;QAClC,IAAI,MAAM,QAAQ,UAAU;SAC1B,UAAU;SACV,eAAe;QACjB;OACF,CAAC;OACD,MAAM,iBAAiB,QAAQ,MAAM;MACvC,CAAC;MACD,MAAM,iBAAiB,GAAG,UAAU,kBAAkB,IAAI;MAC1D,eAAe,QAAQ;MACvB,IAAI,QAAQ;MACZ,eAAe,iBAAiB,eAAe;OAC7C,IAAI,CAAC,OAAO;QACV,QAAQ;QACR,eAAe,cAAc;QAC7B,OAAO,iBAAiB;SACtB,QAAQ;SACR,eAAe,cAAc;QAC/B,GAAG,GAAI;QACP;OACF;OACA,mBAAwB,OAAO,QAAQ,OAAO,CAAC;MACjD,CAAC;MACD,KAAK,OAAO,gBAAgB,cAAc;KAC5C;KACA,OAAO;IACT;IAEA,MAAM,kBAAkB,SAAoB;KAC1C,MAAM,QAAe,MAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,WAAW,CAAC;KACtE,MAAM,SAAgB,MAAM,QAAQ,MAAM,MAAM,IAAI,KAAK,SAAS,CAAC;KACnE,MAAM,WAAkB,MAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,WAAW,CAAC;KACzE,cAAc,cAAc,GAAG,MAAM,OAAO;KAC5C,cAAc,YAAY,aAAa,MAAM,SAAS,OAAO;KAC7D,cAAc,SAAS,MAAM,SAAS;KACtC,aAAa,cAAc;KAG3B,IAAI,CAAC,SAAS,QAAQ;MACpB,KAAK,MAAM,QAAQ,OAAO,aAAa,OAAO,WAAW,MAAM,QAAQ,KAAK,CAAC;MAC7E;KACF;KACA,KAAK,MAAM,WAAW,UAAU;MAC9B,aAAa,OAAO,WAAW,SAAS,MAAM,CAAC;MAC/C,IAAI,gBAAgB,IAAI,OAAO,SAAS,OAAO,EAAE,CAAC,GAAG;MACrD,KAAK,MAAM,QAAQ,MAAM,QAAQ,SAAS,QAAQ,IAAI,QAAQ,WAAW,CAAC,GACxE,aAAa,OAAO,WAAW,MAAM,QAAQ,IAAI,CAAC;KAEtD;IACF;;;;;;;;IASA,MAAM,kBAAkB,IAAY,UAAwB;KAC1D,CAAM,YAAY;MAGhB,YAAY,cAAc,OAAO,MAAM;MACvC,IAAI;OACF,MAAM,OAAO,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;OAAE,CAAC;OACzF,IAAI,MAAM,OAAO,OAAO;QACtB,YAAY,cAAc,QAAQ,MAAM,SAAS;QACjD;OACF;OACA,aAAa,KAAK,IAAI,IAAI;OAC1B,YAAY,cAAc,OACxB,MAAM,QACJ,8CACJ;OACA,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,YAAY,OAAO;QAC7B,YAAY,cAAc,YAAY,QAAQ,UAAU,OAAO;QAC/D;OACF;OACA,YAAY,cAAc,QAAQ,UAC9B,IAAI,MAAM,uCACV,QAAQ,QACN,IAAI,MAAM,2BACV;OACN,MAAM,aAAa;MACrB,SAAS,OAAY;OACnB,YAAY,cAAc,UAAU,OAAO,WAAW;MACxD,UAAU;OACR,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;MAC5C;KACF,EAAA,CAAG;IACL;IAEA,MAAM,cAAc,MAAW,SAAgB,CAAC,GAAG,SAAS,UAAuB;KACjF,MAAM,MAAM,GAAG,MAAM,cAAc,KAAK,WAAW,YAAY,KAAK,SAAS,aAAa,IAAI;KAC9F,MAAM,OAAO,GAAG,OAAO,kBAAkB;KACzC,MAAM,QAAQ,GAAG,OAAO,mBAAmB;KAC3C,MAAM,OAAO,GAAG,QAAQ,KAAA,GAAW,KAAK,SAAS,KAAK,EAAE,CAAC;KACzD,IAAI,KAAK,UAAU,MAAM,OAAO,GAAG,QAAQ,gBAAgB,MAAM,CAAC;KAClE,IAAI,KAAK,YAAY,MAAM,OAAO,GAAG,QAAQ,iBAAiB,OAAO,CAAC;KAEtE,IADgB,KAAK,SAAS,OAAO,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,UAAU,KAAK,IAAI,GAEjG,MAAM,OAAO,GAAG,QAAQ,iBAAiB,SAAS,UAAU,KAAK,MAAM,OAAO,GAAG,CAAC;KAIpF,IAAI,KAAK,iBAAiB,MAAM,OAAO,GAAG,QAAQ,iBAAiB,UAAU,CAAC;KAC9E,KAAK,OAAO,KAAK;KAEjB,MAAM,OAAiB,CAAC;KACxB,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK,OAAO;KACxC,IAAI,KAAK,YAAY,KAAK,KAAK,GAAG,UAAU,KAAK,UAAU,EAAE,IAAI;KACjE,KAAK,KAAK,KAAK,iBAAiB,QAAQ,QAAQ,KAAK,cAAc,MAAM,MAAM;KAC/E,MAAM,aAAoB,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC;KAC9E,KAAK,KAAK,UAAU,mBAAmB,oBAAoB,UAAU,CAAC,GAAG;KACzE,KAAK,OAAO,GAAG,OAAO,oBAAoB,KAAK,KAAK,KAAK,CAAC,CAAC;KAM3D,IAAI,KAAK,iBAGP,KAAK,OACH,GACE,OACA,mBACA,MAAM,QAAQ,KAAK,gBAAgB,EAAE,EAAE,OAAO,KAAK,gBAAgB,SACrE,CACF;KAEF,IAAI,OAAO,IAAI;KAEf,MAAM,UAAU,GAAG,OAAO,qBAAqB;KAG/C,IAAI,KAAK,iBAAiB;MACxB,MAAM,aAAa,GAAG,UAAU,sBAAsB,IAAI;MAC1D,WAAW,QAAQ;MACnB,WAAW,iBAAiB,eAAe,eAAe,KAAK,IAAI,KAAK,SAAS,KAAK,EAAE,CAAC;MACzF,QAAQ,OAAO,UAAU;KAC3B;KACA,IAAI,KAAK,UACP,QAAQ,OAAO,GAAG,QAAQ,YAAY,KAAK,CAAC;UACvC;MACL,MAAM,SAAS,GAAG,UAAU,4BAA4B,IAAI;MAC5D,OAAO,iBAAiB,eAAe,KAAK,gBAAgB,KAAK,EAAE,CAAC;MACpE,QAAQ,OAAO,MAAM;KACvB;KAIA,MAAM,YAAY,GAAG,UAAU,4BAA4B,IAAI;KAC/D,IAAI,UAAU;KACd,UAAU,iBAAiB,eAAe;MACxC,IAAI,SAAS;MACb,UAAU;MACV,MAAM,QAAQ,GAAG,SAAS,gBAAgB;MAC1C,MAAM,QAAQ,KAAK,SAAS;MAC5B,MAAM,cAAc;MACpB,KAAK,OAAO,KAAK;MACjB,MAAM,MAAM;MACZ,IAAI,UAAU;MACd,MAAM,eAAqB;OACzB,IAAI,SAAS;OACb,UAAU;OACV,iBAAsB,KAAK,IAAI,MAAM,KAAK;MAC5C;MACA,MAAM,iBAAiB,YAAY,UAAU;OAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;OAClC,IAAI,MAAM,QAAQ,UAAU;QAC1B,UAAU;QACV,aAAkB;OACpB;MACF,CAAC;MACD,MAAM,iBAAiB,QAAQ,MAAM;KACvC,CAAC;KACD,QAAQ,OAAO,SAAS;KAIxB,MAAM,WAAW,GAAG,UAAU,cAAc;KAC5C,SAAS,QAAQ;KACjB,SAAS,OAAO,IAAI,OAAO,OAAO,EAAE,CAAC;KACrC,KAAK,MAAM,SAAS,QAAQ,SAAS,OAAO,IAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,GAAG,OAAO,OAAO,MAAM,EAAE,CAAC,CAAC;KAC1G,SAAS,QAAQ,OAAO,KAAK,WAAW,EAAE;KAC1C,SAAS,iBAAiB,gBAAgB,KAAK,mBAAmB,KAAK,IAAI,SAAS,KAAK,CAAC;KAC1F,QAAQ,OAAO,QAAQ;KAGvB,MAAM,YAAY,GAAG,UAAU,6BAA6B,IAAI;KAChE,IAAI,QAAQ;KACZ,UAAU,iBAAiB,eAAe;MACxC,IAAI,CAAC,OAAO;OACV,QAAQ;OACR,UAAU,cAAc;OACxB,UAAU,UAAU,IAAI,OAAO;OAC/B,OAAO,iBAAiB;QACtB,QAAQ;QACR,UAAU,cAAc;QACxB,UAAU,UAAU,OAAO,OAAO;OACpC,GAAG,GAAK;OACR;MACF;MACA,kBAAuB,KAAK,EAAE;KAChC,CAAC;KACD,QAAQ,OAAO,SAAS;KACxB,IAAI,OAAO,OAAO;KAClB,OAAO;IACT;;;;;;;;IASA,MAAM,iBAAiB,SAAoB;KACzC,sBAAsB;KACtB,MAAM,YAAY,kBAAkB,IAAI;KACxC,IAAI,aAAa,cAAc,wBAAwB;KACvD,yBAAyB;KACzB,eAAe,IAAI;IACrB;;IAGA,MAAM,uBAA6B;KACjC,IAAI,qBAAqB,eAAe,mBAAmB;IAC7D;;IAGA,MAAM,eAAe,YAA2B;KAC9C,IAAI;MACF,cAAc,MAAM,IAAI,WAAW,CAAC;KACtC,SAAS,OAAY;MACnB,YAAY,cAAc,WAAW,OAAO,WAAW;KACzD,UAAU;MACR,mBAAmB,KAAK,IAAI;KAC9B;IACF;;;;;;;;;;;IAYA,MAAM,eAAe,YAA2B;KAC9C,mBAAmB,KAAK,IAAI;KAC5B,IAAI;MACF,cAAc,MAAM,IAAI,WAAW,CAAC;KACtC,QAAQ,CAER;IACF;;;;;;;;IASA,MAAM,WAAW,OAAO,MAAc,MAAe,WAAkC;KACrF,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,MAAM;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,IAAI;MAAE,CAAC;MAC7E,IAAI,QAAQ,OAAO,OAAO;OACxB,YAAY,cAAc,UAAU,QAAQ,SAAS;OACrD;MACF;MACA,YAAY,cAAc;KAC5B,SAAS,OAAY;MACnB,YAAY,cAAc,UAAU,OAAO,WAAW;KACxD,UAAU;MACR,MAAM,aAAa;KACrB;IACF;IAEA,MAAM,sBAAsB,IAAY,YACtC,SACE,0BACA;KAAE;KAAI;IAAQ,GACd,UAAU,SAAS,iBACrB;IAEF,MAAM,sBAAsB,IAAY,SACtC,SAAS,0BAA0B;KAAE;KAAI;IAAK,GAAG,UAAU,KAAK,KAAK,EAAE,GAAG;IAE5E,MAAM,sBAAsB,OAC1B,SAAS,0BAA0B,EAAE,GAAG,GAAG,6BAA6B;;IAG1E,IAAI,gBAAyC;IAC7C,MAAM,uBAA6B;KACjC,IAAI,iBAAiB,cAAc,aAAa;MAC9C,cAAc,MAAM;MACpB;KACF;KACA,MAAM,QAAQ,GAAG,SAAS,gBAAgB;KAC1C,MAAM,cAAc;KACpB,gBAAgB,OAAO,KAAK;KAC5B,MAAM,MAAM;KACZ,gBAAgB;KAChB,IAAI,UAAU;KACd,MAAM,gBAAsB;MAC1B,UAAU;MACV,gBAAgB;MAChB,MAAM,OAAO;KACf;KACA,MAAM,eAAqB;MACzB,IAAI,SAAS;MACb,MAAM,OAAO,MAAM;MACnB,QAAQ;MACR,IAAI,CAAC,KAAK,KAAK,GAAG;MAClB,SAAc,0BAA0B,EAAE,KAAK,GAAG,SAAS,KAAK,KAAK,EAAE,GAAG;KAC5E;KACA,MAAM,iBAAiB,YAAY,UAAU;MAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;MAClC,IAAI,MAAM,QAAQ,UAAU,QAAQ;KACtC,CAAC;KACD,MAAM,iBAAiB,QAAQ,MAAM;IACvC;;;;;;;IAQA,MAAM,uBAAuB,YAA2B;KACtD,mBAAmB,WAAW;KAC9B,MAAM,WAAW,mBAAmB;KACpC,mBAAmB,cAAc;KACjC,YAAY,cAAc;KAC1B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,qBAAqB;OAAE,QAAQ;OAAQ,MAAM;MAAK,CAAC;MAC5E,IAAI,QAAQ,OAAO,OAAO;OACxB,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,MAAM,SAAS,OAAO,QAAQ,UAAU,CAAC;MACzC,YAAY,cACV,SAAS,IACL,OAAO,QAAQ,WAAW,EAAE,OAAO,QAAQ,UAAU,EAAE,OAAO,OAAO,mBACrE,OAAO,QAAQ,WAAW,EAAE;KACpC,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD,UAAU;MACR,mBAAmB,WAAW;MAC9B,mBAAmB,cAAc;MAEjC,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;KAC5C;IACF;IAEA,mBAAmB,iBAAiB,eAAe,KAAK,qBAAqB,CAAC;IAC9E,YAAY,iBAAiB,eAAe,eAAe,CAAC;IAE5D,MAAM,kBAAkB,OAAO,OAA8B;KAC3D,YAAY,cAAc;KAC1B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;MAAE,CAAC;MAC7F,IAAI,CAAC,QAAQ,IAAI;OACf,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,YAAY,cAAc;MAC1B,MAAM,aAAa;MACnB,MAAM,QAAQ,KAAK;MACnB,YAAY,cAAc;KAC5B,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD;IACF;IAEA,MAAM,oBAAoB,OAAO,OAA8B;KAC7D,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;MAAE,CAAC;MAC7F,IAAI,CAAC,QAAQ,IAAI;OACf,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,YAAY,cAAc;MAC1B,MAAM,aAAa;MACnB,MAAM,QAAQ,IAAI;KACpB,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD;IACF;IAEA,MAAM,mBAAmB,OAAO,IAAY,UAAiC;KAC3E,IAAI;MACF,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU;QAAE;QAAI;OAAM,CAAC;MAAE,CAAC;KACvF,QAAQ,CAAC;KACT,MAAM,aAAa;IACrB;IAEA,cAAc,iBAAiB,eAAe;KAC5C,CAAM,YAAY;MAChB,cAAc,WAAW;MACzB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,OAAO,MAAM,IAAI,cAAc;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACnE,IAAI,MAAM,OAAO,OAAO;QACtB,YAAY,cAAc,QAAQ,MAAM,SAAS;QACjD;OACF;OAGA,aAAa,KAAK,IAAI,IAAI;OAC1B,YAAY,cAAc;OAC1B,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,YAAY,OAAO;QAC7B,YAAY,cAAc,YAAY,QAAQ,UAAU,OAAO;QAC/D;OACF;OACA,IAAI,QAAQ,OACV,YAAY,cAAc,OAAO,UAC7B,0CACA;YAEJ,YAAY,cAAc;OAE5B,MAAM,aAAa;MACrB,SAAS,OAAY;OACnB,YAAY,cAAc,UAAU,OAAO,WAAW;MACxD,UAAU;OACR,cAAc,WAAW;OACzB,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;MAC5C;KACF,EAAA,CAAG;IACL,CAAC;IAED,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,YAAY,cAAc;MAC1B,IAAI;OAEF,MAAM,UAAU,MAAM,eAAe,oBAAoB,GAAG,YAAY;QACtE,MAAM,OAAO,MAAM,IAAI,yBAAyB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QAC9E,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,MAAM,SAAS,UAAU;QACxD,MAAM,EAAE,IAAI,KAAK,GAAG,WAAW;QAC/B,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;OACvC,CAAC;OAED,IAAI,QAAQ,SAAS,SACnB,YAAY,cAAc,OAAO,QAAQ,KAAK;YACzC,IAAI,QAAQ,SAAS,aAC1B,YAAY,cAAc;YACrB;QAEL,MAAM,SAAS,MAAM,IAAI,oBAAoB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QAC3E,MAAM,MAAM,QAAQ,SAAS,gBAAgB,iBAAiB,WAAW,QAAQ,OAAO;QACxF,YAAY,cAAc,QAAQ,KAC9B,GAAG,IAAI,cAAc,OAAO,SAC5B,QAAQ,QAAQ,SAAS;OAC/B;MACF,SAAS,OAAY;OACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;MACtD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAED,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,OAAO,MAAM,aAAa;OAChC,IAAI,CAAC,MAAM;QACT,YAAY,cAAc;QAC1B;OACF;OACA,YAAY,cAAc,QAAQ,KAAK,KAAK;OAC5C,MAAM,SAAS,MAAM,iBAAiB,IAAI;OAC1C,IAAI,OAAO,SAAS,cAAc;QAChC,YAAY,cAAc,QAAQ,OAAO,KAAK,iBAAiB,OAAO,OAAO;QAC7E;OACF;OAGA,MAAM,OAAO,OAAO,SAAS,SAAS,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,SAAS,OAAO,QAAQ;OACxF,MAAM,SAAS,MAAM,IAAI,oBAAoB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,IAAI;OAAE,CAAC;OAC3F,YAAY,cAAc,QAAQ,KAC9B,QAAQ,OAAO,KAAK,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,QAAQ,OAAO,YACjF,QAAQ,QAAQ,SAAS;OAC7B,IAAI,QAAQ,IAAI,MAAM,aAAa;MACrC,SAAS,OAAY;OACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;MACtD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAGD,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,kBAAkB,CAAC;IAC9D,UAAU,OACR,GACE,KACA,YACA,2DACF,CACF;IACA,MAAM,UAAU,GAAG,QAAQ,YAAY,qDAAqD;IAC5F,UAAU,OAAO,OAAO;IACxB,UAAU,OACR,GACE,KACA,YACA,iGACF,CACF;IACA,MAAM,aAAa,GAAG,YAAY,UAAU;IAC5C,WAAW,cAAc;IACzB,UAAU,OAAO,UAAU;IAC3B,MAAM,eAAe,GAAG,OAAO,SAAS;IACxC,aAAa,MAAM,YAAY;IAC/B,MAAM,WAAW,GAAG,UAAU,WAAW,OAAO;IAChD,aAAa,OAAO,QAAQ;IAC5B,UAAU,OAAO,YAAY;IAC7B,gBAAgB,OAAO,SAAS;IAGhC,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,OAAO,CAAC;IAClD,SAAS,OAAO,GAAG,KAAK,YAAY,4BAA4B,CAAC;IACjE,MAAM,cAAc,GAAG,OAAO,SAAS;IACvC,YAAY,MAAM,YAAY;IAC9B,MAAM,cAAc,GAAG,UAAU,WAAW;IAC5C,YAAY,MAAM,WAAW;IAC7B,MAAM,UAAU,GAAG,UAAU,WAAW,MAAM;IAC9C,YAAY,OAAO,aAAa,OAAO;IACvC,SAAS,OAAO,WAAW;IAC3B,MAAM,UAAU,GAAG,OAAO,SAAS;IACnC,QAAQ,MAAM,UAAU;IACxB,SAAS,OAAO,OAAO;IAGvB,MAAM,aAAa,GAAG,OAAO,UAAU;IACvC,WAAW,OAAO,GAAG,OAAO,gBAAgB,YAAY,CAAC;IACzD,MAAM,aAAa,GAAG,MAAM,YAAY;IACxC,WAAW,OAAO,UAAU;IAC5B,MAAM,aAAa,GAAG,KAAK,YAAY,EAAE;IACzC,WAAW,OAAO,UAAU;IAC5B,UAAU,OAAO,YAAY,QAAQ;IAIrC,IAAI,WAAW;IACf,IAAI,WAAW;IACf,IAAI,aAAa;;IAEjB,IAAI,mBAAmB;;IAEvB,IAAI,yBAAyB;;;;;;;IAO7B,IAAI,sBAA2B;IAE/B,MAAM,eAAe;;IAGrB,MAAM,kBAAkB,IAAI,WAAmB;KAC7C,IAAI;MACF,MAAM,MAAM,OAAO,aAAa,QAAQ,YAAY;MACpD,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;MACxC,OAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,KAAK,SAAkB,OAAO,IAAI,CAAC,IAAI,CAAC;KAChF,QAAQ;MAEN,OAAO,CAAC;KACV;IACF,EAAA,CAAG,CAAC;IAEJ,MAAM,+BAAqC;KACzC,IAAI;MACF,OAAO,aAAa,QAAQ,cAAc,KAAK,UAAU,CAAC,GAAG,eAAe,CAAC,CAAC;KAChF,QAAQ,CAER;IACF;IACA,IAAI,kBAAkB;IAEtB,MAAM,YAAY,SAAmC;KACnD,SAAS,cAAc;KACvB,KAAK,MAAM,CAAC,KAAK,UAAU,MACzB,SAAS,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,KAAA,GAAW,KAAK,CAAC;IAEpE;;;;;IAMA,IAAI;IAEJ,MAAM,eAAe,WAAgC;KACnD,WAAW,CAAC,CAAC,OAAO,MAAM;KAC1B,WAAW,CAAC,CAAC,OAAO;KACpB,aAAa,CAAC,CAAC,OAAO;KAItB,MAAM,MAAM,OAAO;KACnB,IAAI,KAAK,aACP,sBAAsB,OAAO,IAAI,WAAW,GAAG,IAAI,YAAY;KAGjE,MAAM,cAAc,WAAY,OAAO,KAAK,aAAa,eAAe,UAAW;KACnF,MAAM,YAAY,aAAa,WAAY,OAAO,KAAK,aAAa,QAAQ,OAAQ;KACpF,IAAI,YAAY,CAAC,OAAO,KAAK,YAAY;MACvC,MAAM,QAAQ,OAAO;MACrB,IAAI,SAAS,CAAC,MAAM,IAAI;OACtB,MAAM,cAAc;OACpB,MAAM,YAAY;MACpB;KACF;KAEA,MAAM,OAA2B,CAAC;KAClC,qBAAqB,OAAO,SAAS,OAAO,MAAM,YAAY,IAAI,OAAO,OAAO,MAAM,YAAY,IAAI;KACtG,WAAW;KACX,kBAAkB,OAAO,OAAO,QAAQ,mBAAmB,CAAC;KAC5D,cAAc;MAAE,SAAS,OAAO,QAAQ;MAAS;KAAgB,CAAC;KAClE,YAAY,OAAO,KAAK;KACxB,KAAK,KAAK,CAAC,UAAU,OAAO,uBAAuB,CAAC,EAAA,CAAG,SAAS,OAAO,QAAQ,IAAI,KAAK,OAAO,SAAS,aAAa,iBAAiB,OAAO,UAAU,CAAC;KACxJ,IAAI,UAAU;MACZ,KAAK,KAAK,CAAC,MAAM,OAAO,KAAK,WAAW,YAAY,CAAC;MACrD,KAAK,KAAK,CAAC,QAAQ,OAAO,KAAK,aAAa,IAAI,KAAK,OAAO,KAAK,UAAU,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC;MAMrG,IADwB,CAAC,OAAO,KAAK,aAAa,CAAC,OAAO,KAAK,gBAE7D,KAAK,KAAK,CAAC,QAAQ,6CAA6C,CAAC;WAEjE,KAAK,KAAK,CAAC,QAAQ,wCAAwC,CAAC;MAE9D,KAAK,KAAK,CACR,UACA,OAAO,KAAK,YACR,UACA,+DACN,CAAC;MACD,IAAI,OAAO,KAAK,WAKd,KAAK,KAAK,CACR,aACA,GAAG,mBAAmB,OAAO,KAAK,UAAU,EAAE,mCAChD,CAAC;MAEH,KAAK,KAAK,CAAC,OAAO,OAAO,KAAK,iBAAiB,gCAAgC,iBAAiB,CAAC;MACjG,KAAK,KAAK,CAAC,YAAY,OAAO,KAAK,YAAY,MAAM,CAAC;MACtD,KAAK,KAAK,CAAC,YAAY,GAAG,OAAO,KAAK,eAAe,EAAE,IAAI,CAAC;MAC5D,IAAI,OAAO,YAAY,KAAK,KAAK,CAAC,SAAS,OAAO,WAAW,KAAK,OAAO,MAAM,OAAO,WAAW,SAAS,IAAI,CAAC;MAI/G,MAAM,aAAa,OAAO,MAAM;MAChC,MAAM,cAAc,OAAO,MAAM;MACjC,IAAI,aACF,KAAK,KAAK,CACR,SACA,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,YAAY,QAAQ,eACxD,CAAC;WACI,IAAI,YACT,KAAK,KAAK,CAAC,SAAS,KAAK,QAAQ,UAAU,EAAE,eAAe,CAAC;MAG/D,MAAM,WAAW,OAAO,QAAQ;MAChC,IAAI,aAAa,KAAA,GACf,KAAK,KAAK,CACR,QACA,OAAO,QAAQ,kBACX,gBAAgB,SAAS,wBACzB,kBAAkB,SAAS,GACjC,CAAC;KAEL,OAAO;MAGL,MAAM,YAAY,OAAO;MACzB,KAAK,KAAK,CACR,QACA,YACI,UAAU,gBACR,+BACA,UAAU,UACR,YAAY,UAAU,QAAQ,mBAC9B,+CACJ,WACE,YACA,4BACR,CAAC;KACH;KACA,IAAI,OAAO,iBACT,KAAK,KAAK,CAAC,QAAQ,GAAG,OAAO,gBAAgB,QAAQ,KAAK,IAAI,KAAK,OAAO,gBAAgB,EAAE,CAAC,CAAC,mBAAmB,GAAG,CAAC;KAEvH,IAAI,OAAO,mBAAmB,OAAO,eAAe,UAAU;MAC5D,MAAM,WAAW,OAAO,cAAc;MACtC,KAAK,KAAK,CACR,QACA,SAAS,SAAS,QAAQ,MAAM,IAAI,YAAY,SAAS,SAAS,MAAM,IAAI,QAAQ,SAAS,cAAc,MAAM,KACnH,CAAC;KACH;KACA,IAAI,OAAO,eAAe,aAAa,OAAO,iBAC5C,KAAK,KAAK,CAAC,QAAQ,OAAO,cAAc,SAAS,CAAC;KAEpD,IAAI,OAAO,eAAe,OAAO,YAAY,SAAS,SAAS,GAC7D,KAAK,KAAK,CAAC,QAAQ,OAAO,OAAO,YAAY,SAAS,OAAO,gBAAgB,OAAO,YAAY,SAAS,KAAK,IAAI,GAAG,CAAC;UACjH,IAAI,OAAO,iBAChB,KAAK,KAAK,CAAC,QAAQ,mBAAmB,CAAC;KAEzC,IAAI,OAAO,aAAa,QAAQ;MAC9B,MAAM,MAAM,YAAY,KAAK,OAAO,YAAY,MAAM;MACtD,KAAK,KAAK,CAAC,WAAW,GAAG,MAAM,oBAAoB,OAAO,OAAO,YAAY,QAAQ,CAAC;KACxF;KACA,IAAI,OAAO,aAAa,YAAY,QAAQ;MAC1C,MAAM,QAAQ,OAAO,YAAY,WAAW,QAAQ,MAAM,gBAAgB,KAAK,CAAC,CAAC;MACjF,KAAK,KAAK,CAAC,QAAQ,GAAG,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO,OAAO,YAAY,WAAW,KAAK,IAAI,GAAG,CAAC;KACpH;KACA,IAAI,OAAO,aAAa,kBAAkB,KAAA,GACxC,KAAK,KAAK,CAAC,aAAa,OAAO,YAAY,gBAAgB,mBAAmB,SAAS,CAAC;KAM1F,MAAM,aAAa,OAAO;KAC1B,IAAI,YAAY;MACd,MAAM,OAAO,WAAW,gBACpB,mBACA,WAAW,UACT,WAAW,WAAW,YAAY,gBAClC,WAAW,WAAW,YAAY;MACxC,KAAK,KAAK,CAAC,QAAQ,GAAG,WAAW,YAAY,KAAK,MAAM,CAAC;KAC3D;KACA,SAAS,IAAI;KAEb,IAAI,CAAC,WAAW,QAAQ,MAAM,WAAW,WAAW;KACpD,IAAI,YAAY;MACd,WAAW,cAAc,WAAW,gBAAgB,YAAY,WAAW,UAAU,KAAK,WAAW,QAAQ,OAAO;MACpH,WAAW,WAAW,CAAC,WAAW,iBAAiB,CAAC,WAAW;MAC/D,WAAW,QAAQ,WAAW,gBAC1B,mBACA,WAAW,UACT,SAAS,WAAW,QAAQ,kCAC5B;KACR,OAAO;MACL,WAAW,WAAW,CAAC;MACvB,WAAW,cAAc,aAAa,YAAY;MAClD,WAAW,QAAQ,WAAW,KAAK;KACrC;KAGA,YAAY,cAAc;KAC1B,IAAI,UAAU;MACZ,YAAY,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,GAAG,OAAO,KAAA,GAAW,OAAO,KAAK,WAAW,YAAY,CAAC;MAClG,YAAY,OACV,GAAG,OAAO,KAAK,MAAM,GACrB,GAAG,OAAO,KAAA,GAAW,OAAO,KAAK,aAAa,IAAI,KAAK,OAAO,KAAK,UAAU,CAAC,CAAC,eAAe,IAAI,IAAI,CACxG;KACF,OACE,YAAY,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,GAAG,OAAO,KAAA,GAAW,eAAe,CAAC;KAEhF,UAAU,WAAW,CAAC;KACtB,UAAU,WAAW,CAAC,YAAY,CAAC;KACnC,UAAU,QAAQ,WAAW,KAAK;KAGlC,IAAI,YAAY,QAAQ,WAAW,OAAO,OAAO,QAAQ;MACvD,YAAY,cAAc;MAC1B,KAAK,MAAM,SAAS,OAAO,QAAQ;OACjC,MAAM,SAAS,SAAS,cAAc,QAAQ;OAC9C,OAAO,QAAQ,MAAM;OACrB,OAAO,cAAc,MAAM;OAC3B,YAAY,OAAO,MAAM;MAC3B;KACF;KAEA,WAAW,cAAc;KACzB,KAAK,MAAM,SAAS,OAAO,QAAQ;MACjC,MAAM,OAAO,GAAG,IAAI;MACpB,KAAK,OAAO,GAAG,OAAO,QAAQ,MAAM,IAAI,CAAC;MACzC,MAAM,WAAW,MAAM,iBAAiB,UACpC,IAAI,MAAM,gBAAgB,QAAA,CAAW,QAAQ,CAAC,EAAE,KAChD,GAAG,KAAK,MAAM,MAAM,gBAAgB,IAAI,EAAE;MAC9C,KAAK,OAAO,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,cAAc,MAAM,WAAW,MAAM,IAAI,SAAS,SAAS,WAAW,CAAC;MAC/G,KAAK,OAAO,GAAG,OAAO,MAAM,MAAM,WAAW,CAAC;MAC9C,WAAW,OAAO,IAAI;KACxB;KACA,WAAW,cACT,2FAEe,OAAO,QAAQ,kBAAkB,EAAE;IAEtD;IAEA,MAAM,UAAU,OAAO,QAAQ,SAAwB;KACrD,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,UAAU,QAAQ,aAAa,IAAI;MAC5D,IAAI,UAAU;MACd,YAAY,MAAM;KACpB,SAAS,OAAY;MACnB,YAAY,UAAU,OAAO,WAAW,SAAS,KAAK;KACxD;IACF;IAKA,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,WAAW,CAAC;IAEtD,MAAM,mBAAqC;KACzC,MAAM,QAAQ,GAAG,SAAS,WAAW;KACrC,MAAM,OAAO;KACb,MAAM,MAAM;KACZ,MAAM,MAAM;KACZ,MAAM,OAAO;KACb,OAAO;IACT;IAEA,MAAM,UAAU,GAAG,OAAO,cAAc;IACxC,MAAM,YAAY,GAAG,SAAS,YAAY;IAC1C,MAAM,YAAY,GAAG,OAAO;IAC5B,UAAU,OAAO;IACjB,MAAM,YAAY,GAAG,QAAQ,kBAAkB;IAC/C,UAAU,OAAO,WAAW,WAAW,GAAG,QAAQ,KAAA,GAAW,iBAAiB,CAAC;IAC/E,QAAQ,OAAO,SAAS;IACxB,SAAS,OAAO,OAAO;IAEvB,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,WAAW;IAC5B,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IAEtB,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,WAAW;IAC5B,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IAEtB,MAAM,eAAe,GAAG,KAAK,YAAY,EAAE;IAC3C,SAAS,OAAO,YAAY;IAE5B,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,SAAS,OAAO,SAAS;IAMzB,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,UAAU,OAAO,GAAG,QAAQ,kBAAkB,WAAW,CAAC;IAC1D,MAAM,cAAc,GAAG,SAAS,WAAW;IAC3C,YAAY,OAAO;IACnB,YAAY,aAAa,cAAc,aAAa;IACpD,YAAY,QAAQ;IACpB,MAAM,cAAc,GAAG,QAAQ,kBAAkB,GAAG;IACpD,UAAU,OAAO,aAAa,WAAW;IACzC,SAAS,OAAO,SAAS;IACzB,MAAM,aAAa,GAAG,KAAK,YAAY,EAAE;IACzC,SAAS,OAAO,UAAU;;IAG1B,MAAM,YAAY,MAAsB,GAAG,OAAO,SAAS,CAAC,KAAK,IAAI,IAAA,CAAQ,QAAQ,IAAI,QAAW,IAAI,IAAI,CAAC,IAAI,IAAI;IAErH,MAAM,uBAA6B;KACjC,MAAM,QAAQ,OAAO,YAAY,KAAK;KACtC,YAAY,cAAc,SAAS,KAAK;KACxC,MAAM,QAAQ,SAAS,OAAY,OAAO,SAAS,MAAU,OAAO;KACpE,WAAW,cACT,YAAY,SAAS,KAAK,EAAE,GAAG,MAAM;IAIzC;IAGA,MAAM,YAAY,GAAG,KAAK,YAAY,EAAE;IACxC,UAAU,cACR;IAGF,SAAS,OAAO,SAAS;IAEzB,YAAY,iBAAiB,SAAS,cAAc;IACpD,YAAY,iBAAiB,gBAAgB;KAC3C,eAAe;KACf,SAAc,EAAE,gBAAgB,OAAO,YAAY,KAAK,EAAE,CAAC;IAC7D,CAAC;IAOD,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,GAAG,SAAS,WAAW;IACxC,SAAS,OAAO;IAChB,SAAS,aAAa,cAAc,aAAa;IACjD,SAAS,QAAQ;IACjB,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IACtB,MAAM,UAAU,GAAG,KAAK,YAAY,EAAE;IACtC,SAAS,OAAO,OAAO;IAEvB,MAAM,sBAA4B;KAChC,MAAM,QAAQ,OAAO,SAAS,KAAK;KACnC,SAAS,cAAc,UAAU,IAAI,QAAQ,GAAG,MAAM;KACtD,QAAQ,cACN,UAAU,IACN,gEACA,cAAc,MAAM;IAE5B;IACA,SAAS,iBAAiB,SAAS,aAAa;IAChD,SAAS,iBAAiB,gBAAgB;KACxC,cAAc;KACd,SAAc,EAAE,cAAc,OAAO,SAAS,KAAK,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IACtD,MAAM,cAAiD,CAAC;IACxD,KAAK,MAAM,QAAQ;KACjB,CAAC,aAAa,IAAI;KAClB,CAAC,YAAY,QAAQ;KACrB,CAAC,QAAQ,IAAI;IACf,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,SAAS,EAAE,gBAAgB,IAAI,CAAC,CAAC;KAC1E,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;IACvB;IACA,SAAS,OAAO,UAAU;IAC1B,MAAM,cAAc,GAAG,KAAK,YAAY,EAAE;IAC1C,SAAS,OAAO,WAAW;IAM3B,MAAM,gBAAgB,GAAG,KAAK;IAC9B,cAAc,MAAM,UAAU;IAC9B,SAAS,OAAO,aAAa;;IAG7B,MAAM,aACJ,OACA,QACA,MACA,QACA,WACgE;KAChE,MAAM,MAAM,GAAG,OAAO,cAAc;KACpC,IAAI,OAAO,GAAG,QAAQ,kBAAkB,KAAK,CAAC;KAC9C,MAAM,KAAK,GAAG,SAAS,WAAW;KAClC,MAAM,KAAK,GAAG,SAAS,WAAW;KAClC,KAAK,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAY;MAC9D,MAAM,OAAO;MACb,MAAM,MAAM,OAAO,OAAO,GAAG;MAC7B,MAAM,MAAM,OAAO,OAAO,GAAG;MAC7B,MAAM,OAAO;MACb,MAAM,aAAa,cAAc,GAAG,QAAQ,OAAO;MACnD,MAAM,QAAQ,GAAG,QAAQ;KAC3B;KACA,MAAM,QAAQ,GAAG,QAAQ,uBAAuB,GAAG;KACnD,MAAM,cAAoB;MACxB,MAAM,cAAc,OAAO,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;KAC/D;KACA,MAAM,gBAAsB;MAG1B,IAAI,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,KAAK,GAAG;OACvC,MAAM,OAAO,GAAG;OAChB,GAAG,QAAQ,GAAG;OACd,GAAG,QAAQ;MACb;MACA,MAAM;KACR;KACA,MAAM,iBAAuB,OAAO,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;KACtE,GAAG,iBAAiB,SAAS,OAAO;KACpC,GAAG,iBAAiB,SAAS,OAAO;KACpC,GAAG,iBAAiB,UAAU,QAAQ;KACtC,GAAG,iBAAiB,UAAU,QAAQ;KACtC,MAAM,OAAO,GAAG,OAAO,gBAAgB;KACvC,KAAK,OAAO,IAAI,EAAE;KAClB,IAAI,OAAO,MAAM,KAAK;KACtB,OAAO;MACL;MACA,MAAM,KAAK,QAAQ;OACjB,GAAG,QAAQ,OAAO,GAAG;OACrB,GAAG,QAAQ,OAAO,GAAG;OACrB,MAAM;MACR;KACF;IACF;IAEA,MAAM,YAAY,UAChB,QACA;KAAE,KAAK;KAAG,KAAK;IAAG,GAClB,MACC,IAAI,OAAO,GAAG,GAAG,GAAG,GAAG,MACvB,IAAI,OAAO,KAAK,SAAS,EAAE,cAAc;KAAE,KAAK;KAAI,KAAK;IAAG,EAAE,CAAC,CAClE;IACA,MAAM,YAAY,UAChB,QACA;KAAE,KAAK;KAAG,KAAK;IAAI,GACnB,MACC,IAAI,OAAO,GAAG,GAAG,GAAG,GAAG,MACvB,IAAI,OAAO,KAAK,SAAS,EAAE,gBAAgB;KAAE,KAAK,KAAK,MAAM,KAAK,GAAI;KAAG,KAAK,KAAK,MAAM,KAAK,GAAI;IAAE,EAAE,CAAC,CAC1G;IACA,MAAM,UAAU,UACd,QACA;KAAE,KAAK;KAAG,KAAK;IAAG,GAClB,QACC,IAAI,OAAQ,MAAM,IAAI,QAAQ,GAAG,GAAG,QAAQ,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,EAAE,MAChE,IAAI,OAAO,KAAK,SAAS,EAAE,cAAc;KAAE,KAAK,KAAK,MAAM,KAAK,GAAI;KAAG,KAAK,KAAK,MAAM,KAAK,GAAI;IAAE,EAAE,CAAC,CACxG;IACA,cAAc,OAAO,UAAU,KAAK,UAAU,KAAK,QAAQ,GAAG;IAC9D,cAAc,OACZ,GACE,KACA,YACA,oFAEF,CACF;IACA,cAAc,OACZ,GACE,KACA,YACA,8FAEF,CACF;IAEA,SAAS,OACP,GACE,KACA,YACA,8EAEF,CACF;IACA,MAAM,UAAU,GAAG,KAAK,yBAAyB,EAAE;IACnD,SAAS,OAAO,OAAO;IACvB,SAAS,OAAO,QAAQ;IAKxB,MAAM,aAAa,GAAG,OAAO,UAAU;IACvC,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,QAAQ,MAAM,CAAC;IAC5C,MAAM,cAAc,GAAG,QAAQ,iBAAiB,SAAS;IACzD,WAAW,OAAO,WAAW;IAC7B,WAAW,OAAO,UAAU;IAC5B,WAAW,OACT,GAAG,KAAK,YAAY,0CAA0C,CAChE;IACA,MAAM,WAAW,GAAG,OAAO,QAAQ;IACnC,WAAW,OAAO,QAAQ;IAC1B,MAAM,iBAAiB,GAAG,KAAK;IAC/B,WAAW,OAAO,cAAc;IAChC,MAAM,YAAY,GAAG,OAAO,SAAS;IACrC,MAAM,mBAAmB,GAAG,UAAU,iBAAiB,MAAM;IAC7D,UAAU,OAAO,gBAAgB;IACjC,WAAW,OAAO,SAAS;IAC3B,MAAM,YAAY,GAAG,KAAK,yBAAyB,EAAE;IACrD,WAAW,OAAO,SAAS;IAC3B,SAAS,OAAO,UAAU;;IAG1B,MAAM,YAAY,QAAkB,gBAAqC;KACvE,MAAM,SAAS,OAAO,SAAS,SAAS,CAAC,CAAC;KAC1C,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,MAAM;KACjC,MAAM,OAAO,MAAM,OAAO;KAC1B,IAAI,MAAM;KACV,OAAO,SAAS,OAAO,UAAU;MAC/B,MAAM,SAAU,QAAQ,MAAO;MAE/B,MAAM,MAAM,YAAY,IAAI,KAAK,IAAI,mBAAiB;MACtD,OAAO,QAAQ,IAAI,OAAO,QAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE,QAAQ,MAAM,OAAA,CAAQ,QAAQ,CAAC,EAAE,YAAY,OAAO,GAAA,CAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,IAAI,QAAQ,QAAQ,IAAI,IAAI,EAAG,CAAC,CAAC,QAAQ,CAAC,EAAE;KACtL,CAAC;KACD,OAAO;KACP,OAAO;IACT;IAEA,MAAM,SAAS,OAAwB,MAAM,MAAS,IAAI,KAAK,IAAA,CAAQ,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAA,CAAM,QAAQ,CAAC,EAAE;IAElH,MAAM,gBAAgB,SAAoB;KACxC,MAAM,QAAQ,OAAO,MAAM,SAAS,CAAC;KACrC,YAAY,cAAc,KAAK,MAAM,SAAS,GAAG;KACjD,YAAY,YAAY,aAAa,QAAQ,IAAI,OAAO;KACxD,SAAS,cAAc;KACvB,eAAe,cAAc;KAC7B,IAAI,UAAU,GAAG;MACf,UAAU,cAAc;MACxB;KACF;KACA,MAAM,QAAQ,KAAa,UAAwB;MACjD,SAAS,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,KAAA,GAAW,KAAK,CAAC;KAClE;KACA,KAAK,QAAQ,GAAG,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,KAAK,UAAU,EAAE,EAAE;KAC3E,MAAM,OAAO,KAAK;KAClB,KACE,UACA,OACI,MAAM,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,KAAK,GAAG,EAAE,QAAQ,MAAM,KAAK,GAAG,EAAE,MAAM,KAAK,QAAQ,KAC1F,MACN;KACA,IAAI,QAAQ,KAAK,MAAM,MACrB,UAAU,cAAc,UAAU,MAAM,KAAK,GAAG,EAAE;KAEpD,MAAM,WAAW,KAAK,YAAY,CAAC;KACnC,KACE,QACA,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO,QAAQ,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,IAAI,GACjH;KACA,KAAK,QAAQ,GAAG,KAAK,WAAW,SAAS,EAAE,SAAS,KAAK,OAAO,KAAK,WAAW,SAAS,KAAK,IAAI,EAAE,IAAI;KAExG,MAAM,YAAmB,MAAM,QAAQ,KAAK,SAAS,IAAI,KAAK,YAAY,CAAC;KAC3E,IAAI,UAAU,SAAS,GAAG;MACxB,MAAM,UAAU,OAAuB;OAErC,MAAM,OADe,MAAM,QAAQ,qBAAqB,QAAQ,IAAI,oBAAoB,WAAW,CAAC,EAAA,CAClF,MAAM,SAAS,KAAK,OAAO,EAAE;OAC/C,OAAO,MAAM,OAAO,IAAI,SAAS,IAAI,WAAW,IAAI,EAAE,IAAI;MAC5D;MACA,KACE,OACA,UACG,MAAM,GAAG,OAAO,OAAO,GAAG,KAAK,KAAK,MAAM,OAAO,GAAG,KAAK,KAAK,EAAE,CAAC,CACjE,KAAK,QAAQ,GAAG,OAAO,IAAI,SAAS,EAAE,GAAG,IAAI,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC,CACzE,KAAK,KAAK,CACf;KACF;KACA,MAAM,SAAmB,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;KAErE,MAAM,8BAAc,IAAI,IAAY;KAEpC,CAD+B,MAAM,QAAQ,KAAK,YAAY,IAAI,KAAK,eAAe,CAAC,EAAA,CAC1E,SAAS,OAAO,UAAU;MACrC,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK;KACtC,CAAC;KACD,eAAe,YAAY,SAAS,QAAQ,WAAW;KACvD,eAAe,OAAO,GAAG,KAAK,YAAY,wBAAwB,KAAK,IAAI,GAAG,GAAG,MAAM,EAAE,IAAI,CAAC;IAChG;IAEA,MAAM,aAAa,YAA2B;KAC5C,IAAI;MACF,aAAa,MAAM,IAAI,kBAAkB,CAAC;KAC5C,SAAS,OAAY;MACnB,UAAU,cAAc,UAAU,OAAO,WAAW;KACtD;IACF;IACA,iBAAiB,iBAAiB,eAAe,KAAK,WAAW,CAAC;IAMlE,MAAM,gBAAgB,GAAG,OAAO,UAAU;IAC1C,cAAc,OAAO,GAAG,OAAO,gBAAgB,SAAS,CAAC;IAEzD,MAAM,eAAe,GAAG,OAAO,cAAc;IAC7C,aAAa,OAAO,GAAG,QAAQ,kBAAkB,QAAQ,CAAC;IAC1D,MAAM,gBAAmD,CAAC;IAC1D,KAAK,MAAM,QAAQ,CACjB,CAAC,YAAY,kBAAkB,GAC/B,CAAC,QAAQ,MAAM,CACjB,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,cAAc,GAAG,CAAC;KAC3D,cAAc,OAAO;KACrB,aAAa,OAAO,GAAG;IACzB;IACA,cAAc,OAAO,YAAY;IAEjC,MAAM,gBAAgB,GAAG,KAAK,YAAY,EAAE;IAC5C,cAAc,OAAO,aAAa;IAClC,MAAM,qBAAqB,GAAG,KAAK,YAAY,EAAE;IACjD,cAAc,OAAO,kBAAkB;IAEvC,MAAM,UAAU,GAAG,OAAO,cAAc;IACxC,MAAM,mBAAmB,GAAG,UAAU,iBAAiB,YAAY;IACnE,QAAQ,OAAO,gBAAgB;IAC/B,cAAc,OAAO,OAAO;IAE5B,MAAM,mBAAmB,GAAG,OAAO,WAAW,EAAE;IAChD,iBAAiB,MAAM,UAAU;IACjC,cAAc,OAAO,gBAAgB;IAErC,MAAM,eAAe,cAAc;IACnC,cAAc,OAAO,aAAa,IAAI;IACtC,cAAc,OAAO,aAAa;IAQlC,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,YAAY,OAAO,GAAG,OAAO,gBAAgB,SAAS,CAAC;IACvD,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,kBAAkB,OAAO,CAAC;IACvD,MAAM,cAAiD,CAAC;IACxD,KAAK,MAAM,QAAQ,CACjB,CAAC,QAAQ,UAAU,GACnB,CAAC,WAAW,YAAY,CAC1B,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,gBAAgB,GAAG,CAAC;KAC7D,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;IACvB;IACA,YAAY,OAAO,UAAU;IAC7B,MAAM,gBAAgB,GAAG,KAAK,YAAY,EAAE;IAC5C,YAAY,OAAO,aAAa;IAChC,MAAM,kBAAkB,GAAG,KAAK,YAAY,EAAE;IAC9C,YAAY,OAAO,eAAe;IAClC,MAAM,aAAa,cAAc;IACjC,YAAY,OAAO,WAAW,IAAI;IAClC,YAAY,OAAO,WAAW;IAE9B,uBAAuB,SAAiB,UAAqB;KAC3D,MAAM,OAAO,YAAY,YAAY,YAAY;KACjD,KAAK,MAAM,OAAO,OAAO,KAAK,WAAW,GACvC,YAAY,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,IAAI;KAE1D,IAAI,OACF,cAAc,cACZ,iBAAiB,OAAO,MAAM,aAAa,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,QACnD,MAAM,MAAM,SAAS,MAAM;UAErC,cAAc,cACZ,SAAS,YACL,qCACA;IAEV;IAEA,MAAM,oBAAoB,SAAoB;KAC5C,sBAAsB,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;KAC/D,MAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;KAEpC,MAAM,OAAO,OAAO,MAAM,gBAAgB,EAAE;KAC5C,gBAAgB,cAAc,OAAO,GAAG,KAAK,SAAS,SAAS;IACjE;IAEA,MAAM,kBAAkB,OAAO,SAA4C;KACzE,WAAW,cAAc;KACzB,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,iBAAiB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;MAAE,CAAC;MAC5F,IAAI,QAAQ,IAAI;OACd,iBAAiB,MAAM;OACvB,WAAW,cACT,MAAM,OAAO,SAAS,YAAY,SAAS,OAAO,UACjD,OAAO,cAAc,QAClB,0BACA;MACR,OACE,WAAW,cAAc,QAAQ,QAAQ,SAAS;KAEtD,SAAS,OAAY;MACnB,WAAW,cAAc,QAAQ,OAAO,WAAW;KACrD;IACF;IAKA,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,OAAO,CAAC;IACnD,MAAM,YAAY,GAAG,OAAO,QAAQ;IACpC,UAAU,OAAO,SAAS;IAC1B,MAAM,YAAY,GAAG,OAAO,SAAS;IACrC,UAAU,MAAM,YAAY;IAC5B,MAAM,YAAY,GAAG,UAAU,iBAAiB,MAAM;IACtD,UAAU,OAAO,SAAS;IAC1B,UAAU,OAAO,SAAS;IAC1B,MAAM,YAAY,GAAG,KAAK,yBAAyB,EAAE;IACrD,UAAU,OAAO,SAAS;IAC1B,UAAU,OACR,GAAG,KAAK,YAAY,qDAAmD,CACzE;IACA,UAAU,OAAO,SAAS;IAE1B,MAAM,iBAAiB,SAAoB;KACzC,UAAU,cAAc;KACxB,UAAU,OAAO,GAAG,OAAO,KAAK,MAAM,GAAG,GAAG,OAAO,KAAA,GAAW,MAAM,WAAW,IAAI,CAAC;KACpF,UAAU,OACR,GAAG,OAAO,KAAK,OAAO,GACtB,GACE,OACA,KAAA,GACA,MAAM,kBAAkB,IACpB,KAAK,KAAK,MAAM,KAAK,kBAAkB,GAAM,EAAE,iBAC/C,KACN,CACF;IACF;IAEA,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,UAAU,cAAc;MACxB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,iBAAiB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACxE,IAAI,CAAC,QAAQ,IAAI;QACf,UAAU,cAAc,QAAQ,QAAQ,SAAS;QACjD;OACF;OACA,MAAM,UAAU,OAAO;OACvB,cAAc;QAAE;QAAS;OAAgB,CAAC;OAC1C,IAAI,OAAO,WACT,UAAU,cAAc,SAAS,OAAO,OAAO,MAAM,QAAQ,GAC3D,OAAO,cAAc,MAAM,UAAU,OAAO,WAAW,EAAE,OAAO,KAC/D,OAAO,MAAM,MAAM,OAAO,QAAQ;YAErC,UAAU,cAAc,UAAU,QAAQ;MAE9C,SAAS,OAAY;OACnB,UAAU,cAAc,QAAQ,OAAO,WAAW;MACpD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,MAAM,CAAC;IAClD,UAAU,OACR,GAAG,KAAK,YAAY,0DAA0D,CAChF;IACA,MAAM,UAAU,GAAG,OAAO,QAAQ;IAClC,UAAU,OAAO,OAAO;IACxB,UAAU,OACR,GAAG,KAAK,YAAY,mDAAmD,CACzE;IACA,UAAU,OAAO,SAAS;IAE1B,MAAM,eAAe,UAAqB;KACxC,QAAQ,cAAc;KACtB,MAAM,OAA2B;MAC/B,CAAC,OAAO,OAAO,YAAY,MAAM;MACjC,CAAC,QAAQ,OAAO,WAAW,GAAG,MAAM,SAAS,kBAAkB,MAAM;MACrE,CAAC,QAAQ,OAAO,UAAU,MAAM;MAChC,CAAC,QAAQ,OAAO,WAAW,GAAG,MAAM,SAAS,aAAa,MAAM;KAClE;KACA,KAAK,MAAM,CAAC,KAAK,UAAU,MACzB,QAAQ,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,YAAY,KAAK,CAAC;IAEpE;IAEA,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,aAAa,CAAC;IACxD,SAAS,OACP,GACE,KACA,YACA,mDACF,CACF;IACA,SAAS,OACP,GACE,KACA,YACA,yHAGF,CACF;IACA,SAAS,OACP,GACE,KACA,YACA,oIAGF,CACF;IACA,UAAU,OAAO,QAAQ;IAEzB,MAAM,sBAAsB,SAAoB;KAC9C,IAAI,CAAC,MAAM;KACX,MAAM,YAAY,OAAO,KAAK,aAAa,KAAK,aAAa,UAAU;KACvE,MAAM,YAAY,OAAO,KAAK,aAAa,SAAS;KACpD,KAAK,MAAM,OAAO,OAAO,KAAK,aAAa,GACzC,cAAc,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,SAAS;KAGjE,MAAM,YAAY,KAAK,sBAAsB;KAC7C,cAAc,SAAS,WAAW,CAAC;KACnC,cAAc,SAAS,QAAQ,YAC3B,KACA;KAEJ,MAAM,QAAQ,CACZ,cAAc,aAAa,sBAAsB,iBACnD;KACA,IAAI,KAAK,UAAU,MAAM,KAAK,iCAAiC;KAC/D,IAAI,cAAc,YAAY,MAAM,KAAK,kCAAkC;KAC3E,cAAc,cAAc,MAAM,KAAK,KAAK;KAC5C,mBAAmB,cAAc,OAAO,KAAK,QAAQ,EAAE;IACzD;IAEA,MAAM,gBAAgB,OAAO,SAA6C;KACxE,aAAa,cAAc;KAC3B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,cAAc;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;MAAE,CAAC;MACpG,IAAI,QAAQ,IAAI;OACd,mBAAmB,MAAM;OACzB,aAAa,cACX,QAAQ,OAAO,cAAc,aAAa,iBAAiB,aAAa,UACvE,OAAO,cAAc,QAClB,0BACA;MACR,OACE,aAAa,cAAc,QAAQ,QAAQ,SAAS;KAExD,SAAS,OAAY;MACnB,aAAa,cAAc,QAAQ,OAAO,WAAW;KACvD;IACF;IAEA,iBAAiB,iBAAiB,eAAe;KAC/C,CAAM,YAAY;MAChB,iBAAiB,WAAW;MAC5B,iBAAiB,MAAM,UAAU;MACjC,iBAAiB,cAAc;MAC/B,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,0BAA0B;QACjD,QAAQ;QACR,MAAM,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;OACxC,CAAC;OACD,MAAM,QAAkB,CAAC;OACzB,KAAK,MAAM,QAAQ,QAAQ,WAAW,CAAC,GAAG;QACxC,MAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE;QACpC,IAAI,MAAM,WAAW,GAAG,GACtB,MAAM,KAAK,aAAa,KAAK,OAAO,IAAI,SAAS,KAAK,gBAAgB,KAAK;aACtE,IAAI,MAAM,WAAW,GAAG,GAC7B,MAAM,KACJ,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO,KAAK,UAAU,EAAE,UAAU,KAAK,eAAe,OAAO,OAC/F;aACK,IAAI,MAAM,WAAW,GAAG,GAC7B,MAAM,KAAK,cAAc,KAAK,UAAU,IAAI,IAAI,KAAK,KAAK,OAAQ,KAAK,SAAS,OAAQ;OAE5F;OACA,iBAAiB,cAAc,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI,KAAK,UAAU,MAAM;MACxF,SAAS,OAAY;OACnB,iBAAiB,cAAc,QAAQ,OAAO,WAAW;MAC3D,UAAU;OACR,iBAAiB,WAAW;MAC9B;KACF,EAAA,CAAG;IACL,CAAC;IAED,CAAM,YAAY;KAChB,IAAI;MACF,mBAAmB,MAAM,IAAI,YAAY,CAAC;KAC5C,QAAQ;MACN,aAAa,cAAc;KAC7B;KACA,IAAI;MACF,iBAAiB,MAAM,IAAI,eAAe,CAAC;KAC7C,QAAQ;MACN,WAAW,cAAc;KAC3B;IACF,EAAA,CAAG;IAGH,aAAkB;IAElB,WAAgB;IAEhB,QAAa,IAAI;;IAGjB,MAAM,aAAa,MAAiB;KAClC,IAAI,CAAC,GAAG;KACR,UAAU,UAAU,CAAC,CAAC,EAAE;KAExB,MAAM,MAAM,OAAO,EAAE,iBAAiB,GAAK;KAC3C,SAAS,MAAM;KACf,SAAS,MAAM;KACf,MAAM,KAAK,OAAO,EAAE,wBAAwB,GAAI;KAChD,MAAM,KAAK,OAAO,EAAE,wBAAwB,EAAE;KAC9C,SAAS,QAAQ,OAAO,EAAE;KAC1B,SAAS,QAAQ,OAAO,EAAE;KAC1B,SAAS,cAAc,GAAG,GAAG;KAC7B,SAAS,cAAc,GAAG,GAAG;KAC7B,aAAa,cACX,OAAO,KACH,QAAQ,GAAG,6CACX,SAAS,GAAG,GAAG,GAAG,gBAAgB,KAAK,OAAO,KAAK,MAAM,CAAC,EAAE;KAElE,UAAU,cAAc;KACxB,UAAU,OAAO,GAAG,QAAQ,kBAAkB,IAAI,CAAC;KACnD,MAAM,UACJ,EAAE,WAAW;MAAC;OAAE,KAAK;OAAO,KAAK;MAAM;MAAG;OAAE,KAAK;OAAO,KAAK;MAAM;MAAG;OAAE,KAAK;OAAO,KAAK;MAAM;KAAC;KAClG,KAAK,MAAM,UAAU,SAAS;MAC5B,MAAM,YACJ,OAAO,SAAS,EAAE,wBAAwB,QAAU,OAAO,SAAS,EAAE,wBAAwB;MAChG,MAAM,MAAM,GACV,UACA,4BACA,GAAG,OAAO,IAAI,GAAG,OAAO,IAAI,IAAI,YAAY,SAAS,IACvD;MACA,IAAI,iBAAiB,eACnB,KAAK,SAAS;OAAE,sBAAsB,OAAO;OAAK,sBAAsB,OAAO;MAAI,CAAC,CACtF;MACA,IAAI,aAAa,EAAE,yBAAyB,OAAO,OAAO,EAAE,yBAAyB,OAAO,KAC1F,IAAI,UAAU,IAAI,QAAQ;MAE5B,UAAU,OAAO,GAAG;KACtB;KAEA,MAAM,YAAY,EAAE,wBAAwB;MAAE,KAAK;MAAS,KAAK;KAAU;KAC3E,YAAY,MAAM,OAAO,UAAU,GAAG;KACtC,YAAY,MAAM,OAAO,UAAU,GAAG;KACtC,YAAY,OAAO;KACnB,YAAY,QAAQ,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,UAAU,GAAG;KACvF,eAAe;KAEf,MAAM,YAAY,EAAE,sBAAsB;MAAE,KAAK;MAAG,KAAK;KAAI;KAC7D,SAAS,MAAM,OAAO,UAAU,GAAG;KACnC,SAAS,MAAM,OAAO,UAAU,GAAG;KACnC,SAAS,OAAO;KAChB,SAAS,QAAQ,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE;KACrE,cAAc;KAEd,MAAM,OAAO,EAAE,SAAS,QAAQ;KAChC,KAAK,MAAM,OAAO,OAAO,KAAK,WAAW,GACvC,YAAY,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,IAAI;KAG1D,MAAM,aAAa,SAAS;KAC5B,cAAc,MAAM,UAAU,aAAa,KAAK;KAChD,IAAI,YAAY;MACd,MAAM,aAAa,EAAE,SAAS,cAAc,EAAE,iBAAiB;MAC/D,IAAI,YAAY,UAAU,IAAI,WAAW,KAAK,WAAW,GAAG;MAC5D,MAAM,aAAa,EAAE,SAAS,cAAc,EAAE,iBAAiB;MAC/D,IAAI,YAAY,UAAU,IAAI,KAAK,MAAM,WAAW,MAAM,GAAI,GAAG,KAAK,MAAM,WAAW,MAAM,GAAI,CAAC;MAClG,MAAM,WAAW,EAAE,SAAS,YAAY,EAAE,iBAAiB;MAC3D,IAAI,UAAU,QAAQ,IAAI,EAAE,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,CAAC;KAChG;KACA,YAAY,cACV,SAAS,SACL,2BACA,SAAS,cACP,qCACA,YAAY,EAAE,SAAS,aAAa,EAAE,UACnC,KAAK,OAAO,EAAE,SAAS,WAAW,OAAU,GAAI,EAAE;IAE/D;IAEA,MAAM,WAAW,OAAO,UAUH;KACnB,QAAQ,cAAc;KACtB,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,SAAS;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,KAAK;MAAE,CAAC;MACjF,IAAI,QAAQ,IAAI;OACd,UAAU,MAAM;OAChB,QAAQ,cACN,OAAO,OAAO,kBAAkB,cAAc,KAAK,QAC7C,OAAO,qBAAqB,GAAG,OAAO,qBAAqB,UAC3D,OAAO,kBAAkB,OAAO,SAAS,QAAQ,SACtD,OAAO,SAAS,aACb,OAAO,OAAO,QAAQ,WAAW,IAAI,GAAG,OAAO,QAAQ,WAAW,IAAI,UAChE,KAAK,OAAO,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAI,EAAE,GAC5D,KAAK,OAAO,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAI,EAAE,SAC3D,OAAO,QAAQ,WACZ,SAAS,OAAO,QAAQ,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,EAAE,IAAI,OAAO,QAAQ,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,EAAE,KAC3G,WACJ,MACA,MACJ,gBAAgB,SAAS,OAAO,OAAO,kBAAkB,CAAC,CAAC,OAC1D,OAAO,cAAc,QAAQ,OAAO,OAAO,WAAW,aAAa;MACxE,OACE,QAAQ,cAAc,QAAQ,QAAQ,SAAS;KAEnD,SAAS,OAAY;MACnB,QAAQ,cAAc,QAAQ,OAAO,WAAW;KAClD;IACF;IAEA,UAAU,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,iBAAiB,UAAU,QAAQ,CAAC,CAAC;IAEhG,SAAS,iBAAiB,eAAe;KACvC,SAAS,cAAc,GAAG,SAAS,MAAM;IAC3C,CAAC;IACD,SAAS,iBAAiB,eAAe;KACvC,SAAS,cAAc,GAAG,SAAS,MAAM;IAC3C,CAAC;IACD,SAAS,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,sBAAsB,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;IACzG,SAAS,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,sBAAsB,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;IAEzG,CAAM,YAAY;KAChB,IAAI;MACF,UAAU,MAAM,IAAI,OAAO,CAAC;KAC9B,QAAQ;MACN,QAAQ,cAAc;KACxB;IACF,EAAA,CAAG;IAEH,IAAI,aAAa;IAEjB,WAAW,iBAAiB,eAAe;KACzC,CAAM,YAAY;MAChB,WAAW,WAAW;MACtB,YAAY,mCAAmC;MAC/C,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,IACV,YAAY,GAAG,OAAO,WAAW,MAAM,KAAK,GAAG,OAAO,WAAW,OAAO,WAAW,OAAO,EAAE;YAE5F,YAAY,KAAK,QAAQ,WAAW,UAAU,KAAK;MAEvD,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,WAAW,WAAW;MACtB,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;IAED,YAAY,iBAAiB,eAAe;KAC1C,CAAM,YAAY;MAChB,YAAY,WAAW;MACvB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,mBAAmB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OAC1E,IAAI,QAAQ,IACV,YACE,eAAe,OAAO,IAAI,uFAG5B;YAEA,YAAY,QAAQ,QAAQ,WAAW,OAAO,aAAa,QAAQ,OAAO,+BAA+B,KAAK;MAElH,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,YAAY,WAAW;KACzB,EAAA,CAAG;IACL,CAAC;IAED,WAAW,iBAAiB,eAAe;KACzC,CAAM,YAAY;MAChB,WAAW,WAAW;MAGtB,WAAW,QAAQ,OAAO;MAC1B,YACE,0DACF;MACA,IAAI;OAIF,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,CAAC;OAAE,CAAC;OACpG,IAAI,QAAQ,SAAS,WAAW;QAE9B,IAAI,OAAO,IACT,YAAY,GAAG,OAAO,WAAW,MAAM,KAAK,GAAG,OAAO,UAAU,OAAO,UAAU,IAAI,OAAO,QAAQ,KAAK,MAAM,OAAO,WAAW,OAAO,EAAE;aAE1I,YAAY,KAAK,OAAO,WAAW,SAAS,OAAO,UAAU,KAAK,MAAM,KAAK;OAEjF,OAAO,IAAI,CAAC,QAAQ,SAClB,YACE,QAAQ,WAAW,iBACf,0EACA,QAAQ,QAAQ,UAAU,UAC9B,KACF;YAEA,YAAY,sBAAsB;MAEtC,SAAS,OAAY;OACnB,YAAY,YAAY,OAAO,WAAW,SAAS,KAAK;MAC1D;MACA,OAAO,WAAW,QAAQ;MAC1B,aAAa,KAAK,IAAI,IAAI;MAC1B,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;IAED,SAAS,iBAAiB,eAAe;KACvC,CAAM,YAAY;MAChB,MAAM,MAAM,WAAW,MAAM,KAAK;MAClC,IAAI,CAAC,KAAK;OACR,YAAY,kBAAkB,KAAK;OACnC;MACF;MACA,MAAM,CAAC,WAAW,GAAG,QAAQ,IAAI,MAAM,IAAI;MAC3C,MAAM,QAAQ,UAAU,KAAK;MAC7B,MAAM,SAAS,KAAK,KAAK,GAAG,CAAC,CAAC,KAAK;MACnC,SAAS,WAAW;MACpB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,gBAAgB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU;SAAE;SAAO;QAAO,CAAC;OAAE,CAAC;OACpG,IAAI,QAAQ,IAAI;QACd,IAAI,OAAO,OACT,YAAY,MAAM,OAAO,SAAS,EAAE;aAEpC,YAAY,KAAK,OAAO,UAAU,MAAM,OAAO,QAAQ,OAAO,GAAG,iBAAiB,IAAI;QAExF,WAAW,QAAQ;OACrB,OACE,YAAY,UAAU,QAAQ,SAAS,UAAU,KAAK;MAE1D,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,SAAS,WAAW;MACpB,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;;;;;;IAOD,MAAM,cAAc,QAA2B,OAAe,cAAsB,QAA0B;KAC5G,IAAI,QAAQ;KACZ,IAAI;KACJ,MAAM,cAAoB;MACxB,QAAQ;MACR,IAAI,UAAU,KAAA,GAAW,OAAO,aAAa,KAAK;MAClD,OAAO,cAAc;MACrB,OAAO,UAAU,OAAO,OAAO;KACjC;KACA,OAAO,cAAc;KACrB,OAAO,iBAAiB,eAAe;MACrC,IAAI,CAAC,OAAO;OACV,QAAQ;OACR,OAAO,cAAc;OACrB,OAAO,UAAU,IAAI,OAAO;OAC5B,QAAQ,OAAO,WAAW,OAAO,GAAK;OACtC;MACF;MACA,MAAM;MACN,IAAI;KACN,CAAC;IACH;IAEA,MAAM,YAAY,cAA6B;KAC7C,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,UAAU,WAAW;MACrB,IAAI;OACF,MAAM,IAAI,WAAW;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OAEnD,WAAW,QAAQ;OACnB,YAAY,YAAY,uBAAuB,0BAA0B;OACzE,MAAM,QAAQ,KAAK;OACnB,IAAI,WAAW;QACb,MAAM,SAAS,MAAM,IAAI,kBAAkB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QACzE,IAAI,QAAQ,YAAY,OACtB,YAAY,kBAAkB,QAAQ,UAAU,OAAO,kBAAkB,KAAK;aACzE;SACL,aAAa,KAAK,IAAI,IAAI;SAC1B,YAAY,oCAAoC;QAClD;OACF;MACF,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD,UAAU;OACR,MAAM,QAAQ,KAAK;MACrB;KACF,EAAA,CAAG;IACL;IAEA,WAAW,WAAW,UAAU,eAAe,SAAS,KAAK,CAAC;IAC9D,WAAW,WAAW,aAAa,kBAAkB,SAAS,IAAI,CAAC;IAEnE,QAAQ,iBAAiB,eAAe;KACtC,CAAM,YAAY;MAChB,QAAQ,WAAW;MACnB,QAAQ,MAAM,UAAU;MACxB,QAAQ,YAAY;MACpB,QAAQ,cAAc;MACtB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,SAAS;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,OAAO,YAAY,MAAM,CAAC;OAAE,CAAC;OACxG,IAAI,QAAQ,IAAI;QACd,QAAQ,YAAY;QACpB,QAAQ,cAAc,KAAK,OAAO,GAAG,OAAO,OAAO,MAAM,IAAI,OAAO,QAAQ,UAAU,OAAO,YAAY,YAAY,OAAO,cAAc;OAC5I,OAAO;QACL,QAAQ,YAAY;QACpB,QAAQ,cAAc,KAAK,QAAQ,OAAO,IAAI,OAAO,KAAK,MAAM,KAAK,QAAQ,SAAS;OACxF;MACF,SAAS,OAAY;OACnB,QAAQ,YAAY;OACpB,QAAQ,cAAc,KAAK,OAAO,WAAW;MAC/C;MACA,QAAQ,WAAW;KACrB,EAAA,CAAG;IACL,CAAC;IAED,WAAW,iBAAiB,eAAe,KAAK,QAAQ,KAAK,CAAC;IAE9D,KAAK,OAAO,IAAI;IAChB,QAAa,KAAK;IAElB,MAAM,iBAAiB,OAAO,YAAY,YAAY,GAAM;IAE5D,QAAQ,OAAO,kBAAkB;KAC/B,MAAM,SAAS,cAAc,KAAK,IAAI,IAAI;KAI1C,IAAI,mBAAmB,KAAK,IAAI,GAAG,kBAAkB,MAAM,GAAG,aAAkB;KAChF,IAAI,QAAQ;MACV,QAAa,IAAI;MACjB;KACF;KAEA,IAAI,KAAK,OAAO,IAAI,KAAM,QAAa,KAAK;IAC9C,GAAG,GAAI;IAEP,aAAa;KACX,WAAW;KACX,IAAI,UAAU,KAAA,GAAW,OAAO,cAAc,KAAK;KACnD,OAAO,cAAc,cAAc;IACrC;GACF,GAAG,CAAC,CAAC;GAEL,QAAA,GAAOC,MAAAA,cAAAA,CAAc,OAAO,EAAE,KAAK,QAAQ,CAAC;EAC9C;EAEA,SAAgB,MAAM,KAA0B;GAC9C,IAAI,aAEA,IAAI,MAAM,OAAO,0BACf,IAAI,MAAM,SAAS;IAAE,MAAM;IAAoB,IAAI;IAAsB,OAAO;IAAI,aAAa;GAAgB,UAAA,GAC/GA,MAAAA,cAAAA,CAAc,KAAK,CACrB,CACF,GACF,mCACF;EACF"}
1
+ {"version":3,"file":"client.js","names":["useRef","createElement"],"sources":["../src/file-picker.ts","../src/cookies.ts","../src/account-sync.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 浏览器侧的文件「打开 / 另存为」封装。\n *\n * 为什么需要单独一个模块:插件宿主跑在 Electron 的 **utility 进程**里,\n * 那里 `require('electron')` 只暴露 `net` 与 `systemPreferences`(见 net-diagnostics\n * 的能力探测),而 `dialog` 是**主进程**模块 —— 插件够不到。\n * 也就是说\"弹一个系统文件框让人选\"这件事,宿主侧做不到,\n * 只能由**渲染进程**(插件界面所在的地方)用 Chromium 自己的能力完成:\n *\n * 打开文件:`<input type=\"file\">` —— 任何 Chromium 都支持,最稳\n * 另存为: `window.showSaveFilePicker()` —— File System Access API,Electron 有实现\n * 拿真实路径:`window.__DSH_DESKTOP_FILE_PATH__` —— DSH 注入的 preload 桥\n * (`webUtils.getPathForFile`,注释写明\"只解析操作者选中的、真实落盘的 File\")\n *\n * 三者都做**能力探测 + 优雅回退**,绝不把功能锁死:拿不到就走宿主侧写文件(旧行为),\n * 并如实说明回退原因。\n *\n * 关于「凭证要不要经过 HTTP」这件事的取舍,见 accounts.ts 里\n * `exportAccountsToFile` 与 `exportAccounts` 的注释 —— 那里有完整理由。\n */\n\n/** DSH 注入的 preload 桥(宿主未注入时整个对象不存在)。 */\ninterface DesktopFilePathBridgeWindow {\n __DSH_DESKTOP_FILE_PATH__?: {\n /** 把用户**选中的** File 还原成真实磁盘路径;非磁盘来源的 File 会抛错。 */\n getPathForFile(file: File): string\n }\n showSaveFilePicker?: (options?: {\n suggestedName?: string\n types?: { description?: string; accept: Record<string, string[]> }[]\n }) => Promise<{\n name?: string\n createWritable: () => Promise<{ write: (data: Blob | string) => Promise<void>; close: () => Promise<void> }>\n }>\n}\n\ntype Win = DesktopFilePathBridgeWindow & Record<string, any>\n\nconst win = (): Win => globalThis as unknown as Win\n\n/**\n * 把界面选中的 File 还原成**真实磁盘路径**。\n *\n * 这一步的价值:拿到路径后,宿主可以**自己去读那个文件**,\n * 于是导入的凭证明文根本不经过 HTTP 请求体 —— 只是把\"文件在哪\"告诉宿主。\n * 桥不可用(宿主没注入 / 非磁盘来源的 File)就返回 undefined,调用方改用内容导入。\n */\nexport function resolvePickedPath(file: File | undefined | null, w: Win = win()): string | undefined {\n if (!file) return undefined\n try {\n const resolved = w.__DSH_DESKTOP_FILE_PATH__?.getPathForFile?.(file)\n return typeof resolved === 'string' && resolved.trim() ? resolved : undefined\n } catch {\n // 桥在、但这个 File 不是磁盘来源(比如拖进来的字符串):不该因此把导入弄崩\n return undefined\n }\n}\n\n/** 渲染进程是否支持 File System Access 的「另存为」。 */\nexport function canSaveWithPicker(w: Win = win()): boolean {\n return typeof w?.showSaveFilePicker === 'function'\n}\n\n/**\n * 备份文件建议名。\n *\n * 刻意只用 **数字 / 字母 / 短横线 / 点**:这个字符串会进系统另存为框的\n * `suggestedName`,冒号、斜杠在 Windows 上是非法文件名字符(`toISOString()` 恰好\n * 带冒号,直接拿来当文件名会踩坑)。\n */\nexport function suggestedExportName(now: Date = new Date()): string {\n const pad = (value: number) => String(value).padStart(2, '0')\n const stamp =\n `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +\n `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`\n return `deepseek-accounts-${stamp}.json`\n}\n\nexport type SaveOutcome =\n | { kind: 'saved'; name: string }\n | { kind: 'cancelled' }\n /** 渲染进程没有 File System Access;调用方回退到宿主写文件。 */\n | { kind: 'unsupported' }\n /** 弹框过程本身失败(权限/平台差异等);调用方回退并说明原因。 */\n | { kind: 'failed'; reason: string }\n\n/** 用户取消选择时 Chromium 抛的是 AbortError,要和其他失败区分开(取消不是错误)。 */\nexport function isPickerCancel(error: unknown): boolean {\n return (error as any)?.name === 'AbortError'\n}\n\n/**\n * 弹系统「另存为」,并把 `produce()` 产出的文本写进用户选定的位置。\n *\n * ⚠️ 为什么收一个回调而不是直接收文本:**必须先弹框、再取内容**。\n * Chromium 的瞬时用户激活是有时限的,若调用方先 `await` 一次网络请求\n * (取备份内容)再弹框,很可能被判成\"没有用户手势\"而直接抛错。\n * 把取数包成回调,顺序就锁在这个函数里,调用方想写错都难。\n */\nexport async function saveWithPicker(\n suggestedName: string,\n produce: () => Promise<string>,\n w: Win = win(),\n): Promise<SaveOutcome> {\n if (!canSaveWithPicker(w)) return { kind: 'unsupported' }\n // ⚠️ 要留住 writable 的引用(审计 F22):`write`/`close` 失败时如果不 `abort`,\n // Chromium 会留下一个**未提交的临时文件与句柄**(模拟磁盘写满时实测 abort 从未被调用)。\n // 成功路径走完就把引用清掉,免得 catch 里对已关闭的 writable 再动手。\n let writable:\n | { write(data: Blob | string): Promise<void>; close(): Promise<void>; abort(reason?: unknown): Promise<void> }\n | undefined\n try {\n const handle = await w.showSaveFilePicker!({\n suggestedName,\n types: [{ description: 'JSON 备份', accept: { 'application/json': ['.json'] } }],\n })\n const text = await produce()\n writable = (await handle.createWritable()) as NonNullable<typeof writable>\n await writable.write(new Blob([text], { type: 'application/json' }))\n await writable.close()\n writable = undefined\n return { kind: 'saved', name: typeof handle?.name === 'string' && handle.name ? handle.name : suggestedName }\n } catch (error: any) {\n if (writable) {\n try {\n await writable.abort(error)\n } catch {}\n }\n if (isPickerCancel(error)) return { kind: 'cancelled' }\n return { kind: 'failed', reason: `${error?.name ?? ''} ${error?.message ?? error}`.trim() }\n }\n}\n\n/**\n * 弹系统「打开」框选一个 JSON。返回 undefined 表示用户取消。\n *\n * 用 `<input type=\"file\">` 而不是 `showOpenFilePicker`:前者在**任何** Chromium\n * 环境里都可用(包括跨域 iframe,Chromium 150 起第三方 iframe 是被禁止弹\n * File System Access 文件框的),是我们最可靠的\"让人选文件\"手段。\n */\nexport function pickJsonFile(doc: Document = document): Promise<File | undefined> {\n return new Promise((resolve) => {\n const input = doc.createElement('input')\n input.type = 'file'\n input.accept = '.json,application/json'\n input.style.display = 'none'\n let settled = false\n // R9(0.2.0):兜底超时 —— 没有 cancel 事件的旧环境里,用户关掉选择框后 promise\n // 会永远挂着(旧注释自己承认\"靠它挂着\")。10 分钟足够任何合理操作,超时按取消处理。\n const timer = setTimeout(() => finish(undefined), 10 * 60_000)\n const finish = (file: File | undefined) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n input.remove()\n resolve(file)\n }\n input.addEventListener('change', () => finish(input.files?.[0]))\n // Chromium 113+ 在用户关掉选择框时会发 cancel;没这个事件的环境就靠它挂着\n input.addEventListener('cancel', () => finish(undefined))\n doc.body.appendChild(input)\n input.click()\n })\n}\n\n/** 导入文件的大小上限:与宿主侧一致(实测单账号 ~1.7 KB、账号数上限 500 → ~850 KB,\n * 取 2 MiB 留 2 倍余量)。两边都要有 —— 前端挡是为了不白传,宿主挡才是真边界。 */\nexport const IMPORT_FILE_LIMIT_BYTES = 2 * 1024 * 1024\n\nexport type ImportSource =\n /** 拿到了真实路径:宿主自己读文件,内容不进 HTTP。 */\n | { kind: 'path'; path: string; name: string }\n /** 没拿到路径:退回\"界面读内容再交给宿主\"。 */\n | { kind: 'content'; payload: unknown; name: string }\n | { kind: 'unreadable'; name: string; reason: string }\n\n/**\n * 把用户选中的文件变成\"宿主能用的东西\"。\n *\n * 优先走路径(凭证不进 HTTP),拿不到路径才退回内容 —— 后者是必要的兜底:\n * 宿主没注入路径桥时功能不该整体失效。\n */\nexport async function readImportSource(file: File, w: Win = win()): Promise<ImportSource> {\n const name = file.name || '备份文件'\n // 前端先按大小挡一道(审计 F16):与宿主侧的上限保持一致,避免白传一次才发现太大。\n if (typeof file.size === 'number' && file.size > IMPORT_FILE_LIMIT_BYTES) {\n return {\n kind: 'unreadable',\n name,\n reason: `文件 ${Math.ceil(file.size / 1024)} KiB,超过上限 ${Math.floor(IMPORT_FILE_LIMIT_BYTES / 1024 / 1024)} MiB`,\n }\n }\n const path = resolvePickedPath(file, w)\n if (path) return { kind: 'path', path, name }\n try {\n return { kind: 'content', payload: JSON.parse(await file.text()), name }\n } catch (error: any) {\n return { kind: 'unreadable', name, reason: `${error?.message ?? error}` }\n }\n}\n","/**\n * cookie 过期信息的采集与解读。\n *\n * ## 为什么值得做\n *\n * 插件存下来的凭证里只有 `name=value` 串,**过期时间一直被丢掉** —— 于是\"登录态会不会\n * 因为长期不用而失效\"这件事在界面上完全没有可观察的线索。\n *\n * 实测(2026-09-12,只读、零额度):DeepSeek 的只读端点**不返回 set-cookie**,\n * token 也不轮换,所以**没有任何\"续期\"手段可做**;能做的只有**把过期时间采下来**,\n * 让\"不确定\"缩小一格 —— 至少能回答\"这些 cookie 里哪些本来就是会话级\"。\n *\n * 顺带一个实测结论(决定\"看什么\"):真正鉴权用的是 **`token`**,不是 cookie ——\n * 只发 token 不带 cookie 能通过,只发 cookie 不带 token 直接被拒(40002 Missing Token)。\n * 所以 cookie 的过期时间**不是**登录态寿命的上界;它只告诉我们浏览器侧会先丢哪些。\n *\n * ## 两种来源形状不同,都要认\n *\n * | 来源 | 字段 | 会话级的表示 |\n * | --- | --- | --- |\n * | CDP `Storage.getCookies`(真实 Edge/Chrome) | `expires` | `-1` |\n * | Electron `session.cookies.get()`(插件自开窗口) | `expirationDate` | 字段缺失 |\n *\n * 两者都以 **秒** 为单位(Unix epoch),本模块统一换算成**毫秒**。\n */\n\nexport interface CookieMeta {\n /** cookie 名。 */\n name: string\n /** 所属域(原样保留,便于排查域匹配问题)。 */\n domain: string\n /** 会话级:浏览器关闭即失效(服务端会话另有寿命,不受它约束)。 */\n session: boolean\n /** 持久级 cookie 的到期时间(**毫秒** epoch);会话级或未知 → 不出现该字段。 */\n expiresAt?: number\n}\n\n/**\n * 归一化一个 cookie 的过期形态。两种来源的字段名不同,这里统一。\n *\n * 判定顺序:显式 `session === true` 优先;否则看 `expires` / `expirationDate`。\n * 缺失、非数、`<= 0` **一律当会话级** —— CDP 对会话级给的就是 `-1`,\n * 而\"0\"在 Unix epoch 里也毫无意义,当成有效时间会算出 1970 年。\n */\nexport function readCookieExpiry(raw: any): { session: boolean; expiresAt?: number } {\n if (!raw || typeof raw !== 'object') return { session: true }\n if (raw.session === true) return { session: true }\n const seconds = Number(raw.expires ?? raw.expirationDate)\n if (!Number.isFinite(seconds) || seconds <= 0) return { session: true }\n return { session: false, expiresAt: Math.round(seconds * 1000) }\n}\n\n/**\n * 从任意来源的 cookie 数组里挑出目标域的,转成 `CookieMeta`。\n *\n * ⚠️ `filter` 由调用方给出、且**必须与该处拼 cookie 头的过滤条件逐字一致**:\n * 这些元信息要描述的正是\"请求里实际带上的那批 cookie\"。两条捕获路径原本的过滤条件\n * 不同(真实浏览器那条用 `deepseek`,Electron 那条用 `deepseek.com`),\n * 这里刻意不强行统一 —— 统一就等于改了请求头内容,而那个改动是没法靠单测兜住的。\n */\nexport function pickCookieMeta(\n cookies: readonly any[],\n filter: (domain: unknown) => boolean,\n): CookieMeta[] {\n const out: CookieMeta[] = []\n for (const raw of cookies ?? []) {\n const name = typeof raw?.name === 'string' ? raw.name : ''\n if (!name) continue\n const domain = String(raw?.domain ?? '')\n if (!filter(domain)) continue\n const { session, expiresAt } = readCookieExpiry(raw)\n out.push({ name, domain, session, ...(expiresAt !== undefined ? { expiresAt } : {}) })\n }\n return out\n}\n\n/**\n * 规整\"已经归一化过\"的 `CookieMeta` 数组(从磁盘读账号记录时用)。\n * 形状不对的条目直接丢掉,不抛错 —— 一份坏记录不该让整个账号库读不出来。\n */\nexport function normalizeCookieMetaList(raw: unknown): CookieMeta[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const out: CookieMeta[] = []\n for (const item of raw) {\n if (!item || typeof item !== 'object') continue\n const name = typeof (item as any).name === 'string' ? (item as any).name : ''\n if (!name) continue\n const expiresRaw = Number((item as any).expiresAt)\n const hasExpiry = Number.isFinite(expiresRaw) && expiresRaw > 0\n const session = (item as any).session === true || !hasExpiry\n out.push({\n name,\n domain: typeof (item as any).domain === 'string' ? (item as any).domain : '',\n session,\n ...(session ? {} : { expiresAt: Math.round(expiresRaw) }),\n })\n }\n return out.length > 0 ? out : undefined\n}\n\nexport interface CookieLifeSummary {\n total: number\n sessionCount: number\n persistentCount: number\n /** 持久级里**最晚**到期的那个 —— 用来回答\"浏览器侧还能撑多久\"。 */\n latest?: { name: string; expiresAt: number; daysLeft: number }\n}\n\n/**\n * 汇总结论。**没有记录时返回 `undefined`**(而不是全 0 的对象)——\n * 调用方要区分\"没采到过期信息\"(老记录 / 手动粘 token)和\"采到了、全是会话级\",\n * 这两种情况的界面文案完全不同。\n */\nexport function summarizeCookieLife(\n metas: readonly CookieMeta[] | undefined,\n now: number = Date.now(),\n): CookieLifeSummary | undefined {\n const list = metas ?? []\n if (list.length === 0) return undefined\n const persistent = list.filter((item) => !item.session && Number.isFinite(item.expiresAt))\n let latest: CookieLifeSummary['latest']\n for (const item of persistent) {\n const expiresAt = item.expiresAt as number\n if (!latest || expiresAt > latest.expiresAt) {\n latest = { name: item.name, expiresAt, daysLeft: (expiresAt - now) / 86_400_000 }\n }\n }\n return {\n total: list.length,\n sessionCount: list.length - persistent.length,\n persistentCount: persistent.length,\n ...(latest ? { latest } : {}),\n }\n}\n\n/** 剩余时间的口语化写法。 */\nexport function describeRemaining(daysLeft: number): string {\n if (daysLeft <= 0) return '已过期'\n if (daysLeft >= 1) return `还剩 ${Math.floor(daysLeft)} 天`\n const hours = Math.max(1, Math.round(daysLeft * 24))\n return `还剩 ${hours} 小时`\n}\n\n/**\n * 一句话说清 cookie 的寿命构成(界面直接展示)。\n *\n * 三种情况分开写,因为它们的含义完全不同:\n * - 没记录 → 老记录 / 手动粘 token,下次重新登录会补上;\n * - 全是会话级 → 浏览器侧本来就没有到期时间,看它撑多久没意义;\n * - 有持久级 → 给出最晚那个的剩余时间(**只是浏览器侧的上界,不是登录态寿命**)。\n */\nexport function describeCookieLife(\n summary: CookieLifeSummary | undefined,\n now: number = Date.now(),\n): string {\n if (!summary) return '⚠️ 未记录(重新登录后会补上)'\n const parts = [`${summary.total} 项`]\n parts.push(summary.sessionCount > 0 ? `${summary.sessionCount} 会话级` : '无会话级')\n if (summary.persistentCount > 0) parts.push(`${summary.persistentCount} 持久级`)\n if (summary.latest) {\n // 注意:`summary.latest.daysLeft` 是按生成这份 summary 的时刻算的。\n // 调用方若隔了很久才展示,传 `now` 进来重算即可。\n const daysLeft = (summary.latest.expiresAt - now) / 86_400_000\n parts.push(`${summary.latest.name} ${describeRemaining(daysLeft)}`)\n }\n return parts.join(' · ')\n}\n","/**\n * 账号库的「什么时候该重读」判据 + 内容签名(纯函数,无依赖,便于单测)。\n *\n * ── 为什么要单独有这么一个模块(2026-09-15 用户反馈)──\n *\n * 现象:**新登录一个账号后,账号库列表不会自己更新**,要关掉设置页再打开才看得到。\n *\n * 根因:账号库列表**不在状态轮询(`/status`)里**。面板每 2 秒轮询一次,但只读\n * `/status` 并渲染**当前账号**的信息;列表本身只有 `loadAccounts()` 被显式调用的那几个\n * 时刻才重读(面板初始化 / 切号 / 移除 / 重命名 / 重新登录 / 添加 / 导入)。\n *\n * 而登录捕获**是异步落地的**:\n * - CDP 那条路(宿主在 utility 进程时的常态)要等用户在浏览器里登录完;\n * - 能开 Electron 窗口那条路更直接 —— `openLoginWindow()` 是\"开窗即返回\",\n * 捕获发生在响应之后。\n * ⇒ 「加完账号」的捕获很可能晚于那次 `loadAccounts()`,列表就停在旧快照上,\n * 而且**此后不会再更新**(轮询不碰账号库)。\n *\n * 最直接的证据是代码里一句注释(client 的「登录新账号」处理里):\n * 「期间保持轮询更密一点,万一捕获是异步落地的也能及时刷出来」\n * —— 作者本来就指望轮询兜住这件事,但轮询只读 `/status`,那个兜底**从未生效**。\n *\n * 同源的第二个缺口:探活会更新记录(补上账号名、限制状态、失败标记),\n * 而**账号名是捕获之后由校验拿到的**(见 index.ts 里那段回写),\n * 所以\"列表里只有一串 `acc_xxxxxxxx`\"这件事也要等下一次刷新才消失。\n *\n * 所以修法是:把轮询的兜底做成真的 —— 按下面的节拍重读 `/accounts`,\n * 并用 `accountsSignature()` 判断内容是否真的变了(没变就不重建 DOM,\n * 免得每几秒把用户正在点的按钮换掉)。\n */\n\n/** 登录流程进行中(窗口开着 / 刚操作过)的重读间隔:够快,用户看不到\"没更新\"。 */\nexport const ACCOUNTS_SYNC_ACTIVE_MS = 3_000\n\n/** 空闲时的重读间隔:只为让探活补上的账号名 / 限制状态 / 失败标记自己出现,不必太快。 */\nexport const ACCOUNTS_SYNC_IDLE_MS = 30_000\n\n/**\n * 现在该不该重读账号库。\n *\n * @param now 当前时间戳\n * @param lastSyncedAt 上次重读的时刻(从未读过传 0)\n * @param active 登录流程是否进行中(窗口开着,或在操作后的加速窗口内)\n */\nexport function shouldSyncAccounts(now: number, lastSyncedAt: number, active: boolean): boolean {\n const gap = active ? ACCOUNTS_SYNC_ACTIVE_MS : ACCOUNTS_SYNC_IDLE_MS\n return now - lastSyncedAt >= gap\n}\n\n/**\n * 账号库响应的内容签名 —— 变了才重建列表。\n *\n * 只取**界面真正渲染的东西**(账号数组 + 分组定义 + 分组分区 + 当前账号 id),不取 `footprint`:\n * 后者是给台账页用的文件统计,跟列表无关,把它算进来只会平白触发重建。\n *\n * ⚠️ `groups` / `sections` 必须是**顶层字段显式列进来**:整包 stringify 只覆盖这里写出的键,\n * 漏一个就会出现\"新建了组、列表却不刷新\"—— 与 0.1.63(读错字段名)和 0.1.67(数据压根不在\n * `/status` 里)是同一类坑:**界面渲染什么,签名就得覆盖什么。**\n *\n * 整包 `JSON.stringify` 而不是逐个字段挑:**按构造就是完整的** ——\n * 将来 `/accounts` 多返回一个会被渲染的字段,这里不用改也不会漏。\n * (代价:若哪天往 accounts 里塞了每调用一次都不同的值,会变成每轮都重建;\n * 真出现那种字段,应该在这里显式排除。)\n */\nexport function accountsSignature(payload: unknown): string {\n const data = (payload ?? {}) as { activeId?: unknown; accounts?: unknown; groups?: unknown; sections?: unknown }\n const accounts = Array.isArray(data.accounts) ? data.accounts : []\n const groups = Array.isArray(data.groups) ? data.groups : []\n const sections = Array.isArray(data.sections) ? data.sections : []\n try {\n return JSON.stringify({ activeId: data.activeId ?? null, accounts, groups, sections })\n } catch {\n // 序列化失败时给空串:调用方约定\"空签名 = 每次都重建\",宁可重建也别卡住不更新\n return ''\n }\n}\n","/**\n * dsh-deepseek-web-login — client 设置页(slots: settings.section)。\n *\n * 与 host 通过同源 fetch 通信:/deepseek-web-login/api/*\n * 渲染契约(沿用生态实测结论):槽位 register 必须带 name 字段,\n * 渲染函数返回 React 元素(createElement),React #130 的坑即来自返回非元素。\n */\nimport { createElement, useEffect, useRef, useState } from 'react'\nimport {\n pickJsonFile,\n readImportSource,\n saveWithPicker,\n suggestedExportName,\n} from '../file-picker.ts'\nimport { describeCookieLife, summarizeCookieLife } from '../cookies.ts'\nimport { accountsSignature, shouldSyncAccounts } from '../account-sync.ts'\n\ntype ClientContext = {\n slots: any\n effect: any\n}\n\nexport const inject = ['slots']\n\nconst API = '/deepseek-web-login/api'\n\ninterface StatusPayload {\n provider: string\n registeredProviders?: string[]\n electron: boolean\n loginCapability?: { processType: string; canOpenWindow: boolean; browser: string | null }\n loginWindowOpen: boolean\n loginProgress: { open: boolean; startedAt?: string; captured?: { token: boolean; cookie: boolean; fingerprint: boolean; wasm: boolean }; lastError?: string; finished?: boolean }\n fingerprint?: { at: string; url: string; stripped: string[]; pageUa?: string; pageBrands?: string[]; pageWebdriver?: boolean }\n lastLoginResult?: { ok: boolean; message: string; at: string }\n auth: {\n loggedIn: boolean\n display?: string\n capturedAt?: string\n hasCookie: boolean\n hasFingerprint: boolean\n wasmHost?: string\n unverified?: boolean\n tokenLength?: number\n /** 观测到的账号级限制解除时间(毫秒时间戳)。只在**生成请求被拒**时才学到。 */\n limitUntilMs?: number | null\n limitObservedAt?: string | null\n /** 最近一次主动探活成功的时间(ISO)。 */\n lastVerifiedAt?: string | null\n /** 最近一次主动探活失败。 */\n lastVerifyError?: { at: string; message: string } | null\n /** cookie 的过期构成(捕获时记下)。老记录 / 手动粘 token 时不存在。 */\n cookieLife?: {\n total: number\n sessionCount: number\n persistentCount: number\n latest?: { name: string; expiresAt: number; daysLeft: number }\n }\n }\n validation?: { ok: boolean; error?: string }\n /** 插件的本地状态目录(「关于」页展示用)。 */\n paths?: { webLogin?: string; accounts?: string; ledger?: string }\n models: { id: string; name: string; description: string; modelType: string; thinking: boolean; contextWindow: number }[]\n config: {\n maxPromptChars: number\n idleTimeoutMs: number\n deleteWebSessions: boolean\n allowConcurrent?: boolean\n minRequestIntervalMs?: number\n version?: string\n probeIntervalMs?: number\n }\n}\n\nasync function api(path: string, init?: RequestInit): Promise<any> {\n const response = await fetch(API + path, {\n headers: { 'content-type': 'application/json' },\n ...init,\n })\n return await response.json()\n}\n\n/**\n * 样式表 —— 全部走宿主的主题令牌(`--dsw-alias-*`)。\n *\n * 历史坑(2026-09-12 用户实测):以前用的是 `var(--theme-text, #ddd)` 这套**并不存在**的变量名,\n * 于是每个颜色都落到兜底值上;兜底又全是深色 → 浅色主题下整个面板仍是黑底,\n * 灰色文字贴在深底上几乎读不出来。\n *\n * 现在的做法:\n * 1. 颜色优先取宿主令牌(`--dsw-alias-label-primary` / `bg-layer-*` / `border-l*` / `state-*` …),\n * 这些令牌由 DSH 按当前主题(浅 / 深 / 皮肤)重定义,插件无需自己判断主题;\n * 2. 令牌缺失时(老宿主 / 令牌改名)退回 `--fb-*`,而 `--fb-*` 由 `prefers-color-scheme` 切换,\n * 保证「浅色主题不会出现黑界面」这条底线永远成立;\n * 3. 正文改用系统无衬线字体(原来是等宽字体铺满全屏,观感像终端日志),等宽只留给命令与代码。\n */\nconst styles = `\n.dsw-page{\n/* 兜底色:令牌缺失时才用到,随系统主题切换 */\n--fb-fg:#1f2329;--fb-fg2:#545b66;--fb-fg3:#8b93a3;\n--fb-bg1:#f6f7f9;--fb-bg2:#ffffff;--fb-bd:#e8eaee;--fb-bd2:#d7dae0;\n--fb-hover:#0000000f;--fb-code:#f3f4f6;\n/* 语义层 */\n--fg:var(--dsw-alias-label-primary,var(--fb-fg));\n--fg2:var(--dsw-alias-label-secondary,var(--fb-fg2));\n--fg3:var(--dsw-alias-label-tertiary,var(--fb-fg3));\n--bg1:var(--dsw-alias-bg-layer-1,var(--fb-bg1));\n--bg2:var(--dsw-alias-bg-layer-2,var(--fb-bg2));\n--bd:var(--dsw-alias-border-l1,var(--fb-bd));\n--bd2:var(--dsw-alias-border-l2,var(--fb-bd2));\n--hover:var(--dsw-alias-interactive-bg-hover,var(--fb-hover));\n--code-bg:var(--dsw-alias-markdown-code-block,var(--fb-code));\n--accent:var(--dsw-alias-brand-primary,#3b6ef0);\n--on-accent:var(--dsw-alias-label-primary-foreground,#ffffff);\n--ok:var(--dsw-alias-state-success-primary,#1a9f5a);\n--warn:var(--dsw-alias-state-warn-label,#9a6a00);\n--err:var(--dsw-alias-state-error-primary,#d93025);\n--mono:var(--dsw-alias-font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);\n/* ── 尺度层 ──────────────────────────────────────────────────────────\n 改造前的问题:fontSize 有 5 档(11/12/12.5/13/14)、圆角 6 档(6/8/10/12/999)、\n 间距散落 7 种值(2/4/6/8/9/10/12)。同一个界面里数字一多,眼睛就觉得\"乱\"却\n 说不出哪乱 —— 所以先把尺度收敛,下面所有组件只能引用这里的变量。 */\n--r-lg:12px; /* 卡片等容器 */\n--r-md:8px; /* 按钮 / 输入框 */\n--r-sm:6px; /* 小组件(备注输入、归组下拉) */\n--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:20px;\n\n/* 状态底色:从语义色现算,不再写死 rgba。\n 写死的 rgba 只对其中一档主题成立 —— 按浅色底调出来的那组绿/橙/红贴到深色底上会发脏。 */\n--ok-weak:color-mix(in srgb,var(--ok) 15%,transparent);\n--warn-weak:color-mix(in srgb,var(--warn) 16%,transparent);\n--err-weak:color-mix(in srgb,var(--err) 14%,transparent);\n--accent-weak:color-mix(in srgb,var(--accent) 12%,transparent);\n--accent-hover:color-mix(in srgb,var(--accent) 88%,var(--fg));\n\nfont-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",sans-serif;\nfont-size:13px;line-height:1.6;color:var(--fg);max-width:760px;padding:2px 0 12px;\n-webkit-font-smoothing:antialiased;\n}\n@media (prefers-color-scheme:dark){\n.dsw-page{--fb-fg:#ededed;--fb-fg2:#b6bcc6;--fb-fg3:#858c99;\n--fb-bg1:#212121;--fb-bg2:#2a2a2a;--fb-bd:#333333;--fb-bd2:#3d3d3d;\n--fb-hover:#ffffff14;--fb-code:#1c1c1c}\n}\n/* 页面标题:整页唯一的 display 级文字。字重 600 + 轻微负字距 ——\n 原来是 14px/500,跟 13px 的正文只差 1px,整页因此没有\"起点\"。 */\n.dsw-title{margin:0 0 4px;font-size:15px;font-weight:600;line-height:1.4;letter-spacing:-.01em;color:var(--fg)}\n.dsw-sub{margin:0 0 var(--sp-5);font-size:12px;line-height:1.65;color:var(--fg2);max-width:64ch}\n/* provider 名带连字符,万一折行会断成 deepseek- / web(看着像故障)—— 整词不拆 */\n.dsw-nobreak{white-space:nowrap}\n/* 分组不再用\"七个一模一样的描边盒子\"。\n 盒子本身不表达任何层级,连堆七张就是 AI 默认长相 —— 按 redesign 技能的判据,\n card 只在 elevation 真的表达层级时才用(我们这里没有那种层级)。\n 数据密集面板的正解是「分区标题 + 负空间」:标题吃字重,分组吃间距,\n 行与行之间的细线交给 .dsw-kv 自己承担。这样页面上唯一有实底的块\n 就只剩账号行和反馈条,它们的重要度反而被衬出来了。 */\n.dsw-card{background:transparent;border:0;border-radius:0;padding:0;margin:0 0 var(--sp-5)}\n/* 分区标题:字重整 600。 */\n.dsw-cardhead{display:flex;align-items:center;gap:var(--sp-2);margin:0 0 var(--sp-2);\n font-size:13px;font-weight:600;letter-spacing:-.005em;color:var(--fg)}\n.dsw-card > .name{margin:0 0 var(--sp-2);font-size:13px;font-weight:600;color:var(--fg)}\n.dsw-row{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap}\n.dsw-badge{font-size:11px;font-weight:500;line-height:1.7;padding:1px 9px;border-radius:999px;\n white-space:nowrap;font-variant-numeric:tabular-nums}\n.dsw-badge.on{color:var(--ok);background:rgba(26,159,90,.15);background:var(--ok-weak)}\n.dsw-badge.off{color:var(--warn);background:rgba(200,140,20,.16);background:var(--warn-weak)}\n.dsw-badge.err{color:var(--err);background:rgba(217,48,37,.15);background:var(--err-weak)}\n/* 按钮:hover 不再整体调 opacity —— 那会把文字一起变淡,看着像被禁用了。\n 改成背景微调 + 按下时 1px 位移(物理感);focus-visible 补上键盘可达性。 */\n.dsw-btn{font:inherit;font-size:12px;font-weight:500;line-height:1.5;padding:6px 13px;\nborder-radius:var(--r-md);border:1px solid transparent;background:var(--accent);color:var(--on-accent);\ncursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .1s ease}\n.dsw-btn:hover:not(:disabled){background:var(--accent-hover)}\n.dsw-btn:active:not(:disabled){transform:translateY(1px)}\n.dsw-btn:disabled{opacity:.45;cursor:not-allowed}\n.dsw-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.dsw-btn.ghost{background:transparent;border-color:var(--bd2);color:var(--fg)}\n.dsw-btn.ghost:hover:not(:disabled){background:var(--hover);border-color:var(--bd)}\n.dsw-btn.danger{background:transparent;border-color:var(--bd2);color:var(--err)}\n.dsw-btn.danger:hover:not(:disabled){background:var(--err-weak);border-color:var(--err)}\n.dsw-btn.armed{background:var(--err);border-color:var(--err);color:var(--on-accent)}\n.dsw-btn.armed:hover:not(:disabled){background:color-mix(in srgb,var(--err) 88%,var(--fg))}\n.dsw-input,.dsw-area{width:100%;box-sizing:border-box;font:inherit;font-size:12.5px;\nbackground:var(--bg1);color:var(--fg);border:1px solid var(--bd2);border-radius:var(--r-md);padding:7px 10px;\noutline:none;transition:border-color .15s ease,box-shadow .15s ease}\n.dsw-input:focus,.dsw-area:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-weak)}\n.dsw-area{min-height:68px;resize:vertical;font-family:var(--mono);font-size:12px;line-height:1.5}\n/* 键值表:标签提一档到次级色 —— 原来用三级色,深色主题下几乎读不出来。\n 行距 5→6px;分隔线只做分组暗示,不做表格线。 */\n.dsw-kv{display:grid;grid-template-columns:auto 1fr;gap:0 var(--sp-3);margin:0;align-items:baseline}\n.dsw-kv > *{padding:6px 0;border-top:1px solid var(--bd);min-width:0}\n.dsw-kv > :nth-child(1),.dsw-kv > :nth-child(2){border-top:none;padding-top:2px}\n.dsw-kv .k{color:var(--fg2);font-size:12px;white-space:nowrap}\n.dsw-kv > *:not(.k){color:var(--fg);word-break:break-word}\n.dsw-msg{margin-top:var(--sp-3);padding:10px 12px;border-radius:var(--r-md);background:var(--bg2);\nborder:1px solid var(--bd);border-left:3px solid var(--fg3);white-space:pre-wrap;max-height:240px;\noverflow:auto;font-size:12px;line-height:1.6;color:var(--fg)}\n.dsw-msg.err{border-left-color:var(--err);color:color-mix(in srgb,var(--err) 78%,var(--fg))}\n.dsw-msg.ok{border-left-color:var(--ok)}\n.dsw-models{list-style:none;margin:0;padding:0}\n.dsw-models li{padding:10px 0;border-top:1px solid var(--bd)}\n.dsw-models li:first-child{border-top:none;padding-top:2px}\n.dsw-models .name{font-size:13px;font-weight:500;color:var(--fg)}\n.dsw-models .id{color:var(--fg3);font-size:12px;margin-top:2px;word-break:break-word}\n/* 说明文字:限宽 64ch —— 满宽的一行小字读起来很累,且会拖长整张卡。 */\n.dsw-hint{color:var(--fg3);font-size:12px;line-height:1.7;margin:var(--sp-3) 0 0;max-width:64ch}\n.dsw-gate-row{display:flex;align-items:center;gap:10px;margin:var(--sp-3) 0;flex-wrap:wrap}\n.dsw-gate-label{color:var(--fg2);font-size:12px;min-width:52px;flex:0 0 auto}\n.dsw-gate-value{font-size:12px;color:var(--fg);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n/* 「上下限」成对的一行:两个滑块并排,右边一个合成值(如「6~10 个」)。\n —— 比\"每个边界各占一行\"省一半高度,而且两个边界的相对位置一眼能比出来。 */\n.dsw-range-pair{flex:1 1 240px;display:flex;gap:var(--sp-2);align-items:center;min-width:190px}\n.dsw-gate-pair-value{font-size:12px;color:var(--fg);min-width:78px;text-align:right;font-variant-numeric:tabular-nums}\n.dsw-switch{display:inline-flex;align-items:center;gap:9px;font-size:13px;color:var(--fg);cursor:pointer;user-select:none}\n.dsw-switch input{position:absolute;opacity:0;width:0;height:0}\n.dsw-switch-track{width:34px;height:20px;border-radius:999px;background:var(--bd2);position:relative;transition:background-color .18s ease;flex:0 0 auto}\n.dsw-switch-track::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .18s ease}\n/* 打开态用主色 —— 改造前是 --err(红)。开关的\"开\"表达的是**状态**不是危险,\n 整排红色开关看着像报错。危险色留给真正会造成破坏的动作(armed 按钮)。 */\n.dsw-switch input:checked + .dsw-switch-track{background:var(--accent)}\n.dsw-switch input:checked + .dsw-switch-track::after{transform:translateX(14px)}\n.dsw-switch input:focus-visible + .dsw-switch-track{outline:2px solid var(--accent);outline-offset:2px}\n.dsw-range{-webkit-appearance:none;appearance:none;flex:1 1 160px;height:4px;border-radius:999px;background:var(--bd2);outline:none;cursor:pointer}\n.dsw-range:focus-visible{outline:2px solid var(--accent);outline-offset:3px}\n.dsw-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;border:none;box-shadow:0 1px 3px rgba(0,0,0,.25)}\n.dsw-preset{padding:4px 10px;font-size:12px}\n.dsw-preset.active{background:var(--accent);border-color:transparent;color:var(--on-accent)}\n.dsw-preset.active:hover{background:var(--accent-hover)}\n.dsw-gate-msg{color:var(--fg3);font-size:12px;min-height:1.6em;margin:var(--sp-3) 0 0}\n.dsw-code{display:block;margin:var(--sp-2) 0;padding:8px 10px;background:var(--code-bg);border:1px solid var(--bd);\nborder-radius:var(--r-sm);font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--fg);\noverflow-x:auto;white-space:pre}\n.dsw-msg::-webkit-scrollbar,.dsw-code::-webkit-scrollbar,.dsw-area::-webkit-scrollbar{width:8px;height:8px}\n.dsw-msg::-webkit-scrollbar-thumb,.dsw-code::-webkit-scrollbar-thumb,.dsw-area::-webkit-scrollbar-thumb{\nbackground:var(--bd2);border-radius:4px}\n/* ── 标签页:把原来一页到底的 7 张卡拆成 4 页 ────────────────────────\n 标签栏做成带边框的圆角容器。未选中态改用**颜色**表达(原来是 opacity .7 —— \n 透明度既压暗了文字又让 hover 没有明确的落点),当前页用下划线。 */\n.dsw-tabs{display:inline-flex;gap:2px;border:1px solid var(--bd);border-radius:var(--r-md);\npadding:0 var(--sp-2);margin:var(--sp-1) 0 var(--sp-4);background:var(--bg1)}\n.dsw-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;\npadding:8px 14px;font:inherit;font-size:13px;color:var(--fg2);cursor:pointer;\ntransition:color .15s ease,border-color .15s ease}\n.dsw-tab:hover{color:var(--fg)}\n.dsw-tab.active{color:var(--fg);font-weight:500;border-bottom-color:var(--fg)}\n.dsw-tab:focus-visible{outline:2px solid var(--accent);outline-offset:-3px;border-radius:4px}\n.dsw-pane[hidden]{display:none}\n/* 二级(子页面)标签:**分段胶囊**。\n 刻意和一级标签长得不一样(一级是\"下划线 + 大字号\"),这样一眼能看出自己在第几层;\n 否则两层长得一样、用户会以为回到了同一层。 */\n.dsw-subtabs{display:inline-flex;gap:2px;padding:3px;margin:0 0 var(--sp-4);border-radius:999px;\n background:var(--bg1);border:1px solid var(--bd)}\n.dsw-subtab{appearance:none;border:0;background:transparent;font:inherit;font-size:12px;\n font-weight:500;color:var(--fg2);padding:4px 14px;border-radius:999px;cursor:pointer;\n transition:color .15s ease,background-color .15s ease}\n.dsw-subtab:hover{color:var(--fg)}\n.dsw-subtab:focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n/* 选中态:底色用 bg2 铺在 bg1 上,深色主题里这两档只差一点点,\n 所以必须再加一圈描边(bd2 比 bd 明显)才立得住 —— 实测深色下不加就跟没选中一样。 */\n.dsw-subtab.active{background:var(--bg2);color:var(--fg);font-weight:600;\n box-shadow:inset 0 0 0 1px var(--bd2)}\n/* 调用台账 */\n.dsw-spark{display:block;width:100%;height:40px;margin:var(--sp-2) 0 2px}\n.dsw-spark rect{fill:var(--bd2)}\n.dsw-spark rect.on{fill:var(--accent)}\n.dsw-spark rect.bad{fill:var(--err)}\n.dsw-path{font-family:var(--mono);font-size:11px;color:var(--fg2);word-break:break-all}\n/* 账号库 */\n.dsw-accounts{list-style:none;margin:var(--sp-2) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--sp-2)}\n/* 账号行是可交互的整块 —— 必须给 hover 反馈。改造前它完全静态,鼠标扫过去没有任何回应。\n 当前账号再叠一层主色淡底,跟\"只是鼠标划过\"区分开。 */\n/* 账号行:从\"bg2 卡片里的凹陷块\"改成\"页面上的凸起块\"。\n 卡片没了之后 bg1 跟页面同色看不见,必须换成 bg2 —— 顺带让账号库成为\n 页面上唯一有实底的一组,重要度自然凸显。 */\n.dsw-account{display:flex;align-items:center;gap:10px;flex-wrap:wrap;border:1px solid var(--bd);\nborder-radius:var(--r-md);padding:10px 12px;background:var(--bg2);\ntransition:background-color .15s ease,border-color .15s ease}\n.dsw-account:hover{background:var(--hover)}\n.dsw-account.active{border-color:var(--accent);background:var(--accent-weak)}\n.dsw-account.active:hover{background:color-mix(in srgb,var(--accent) 16%,transparent)}\n.dsw-account-main{flex:1 1 200px;min-width:0}\n.dsw-account-title{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:13px;font-weight:500}\n.dsw-account-meta{font-size:11px;color:var(--fg3);margin-top:3px;word-break:break-all;font-variant-numeric:tabular-nums}\n.dsw-account-fix{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap;margin-top:6px}\n.dsw-account-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}\n.dsw-labelinput{font:inherit;font-size:12px;padding:3px 7px;border-radius:var(--r-sm);border:1px solid var(--bd2);\nbackground:var(--bg2);color:var(--fg);width:150px;margin-top:4px}\n.dsw-labelinput:focus-visible{outline:2px solid var(--accent);outline-offset:1px}\n.dsw-limit{color:var(--warn)}\n/* 分组:组标题行 + 组内缩进 + 归组下拉 */\n.dsw-grouphead{display:flex;align-items:center;gap:var(--sp-2);margin:var(--sp-3) 0 0;font-size:12px}\n.dsw-grouphead .gname{font-weight:600}\n.dsw-grouphead .gcount{color:var(--fg3);font-size:11px;font-variant-numeric:tabular-nums}\n.dsw-grouphead .gtoggle{cursor:pointer;user-select:none;color:var(--fg2);width:12px;display:inline-block;text-align:center}\n.dsw-grouphead .gtoggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}\n.dsw-grouphead .gspacer{flex:1 1 auto}\n.dsw-grouphead .dsw-btn{font-size:11px;padding:1px 7px}\n.dsw-account.grouped{margin-left:14px}\n.dsw-groupsel{font:inherit;font-size:11px;padding:2px 4px;border-radius:var(--r-sm);border:1px solid var(--bd2);\nbackground:var(--bg2);color:var(--fg);max-width:110px}\n.dsw-groupsel:focus-visible{outline:2px solid var(--accent);outline-offset:1px}\n/* 操作反馈:不归属任何一页,常驻在标签栏之上 */\n.dsw-alert{margin:0 0 var(--sp-3);padding:9px 12px;border-radius:var(--r-md);border:1px solid var(--bd);\nbackground:var(--bg2);white-space:pre-wrap;font-size:12px}\n/* 整行染成纯错误色在深色底上会刺眼且发暗;混一点正文色压回来,色条仍然说明性质。 */\n.dsw-alert.ok{border-left:3px solid var(--ok);color:color-mix(in srgb,var(--ok) 72%,var(--fg))}\n.dsw-alert.err{border-left:3px solid var(--err);color:color-mix(in srgb,var(--err) 78%,var(--fg))}\n/* 用户开了\"减少动态效果\"就别再让元素滑来滑去。 */\n@media (prefers-reduced-motion:reduce){\n.dsw-page *{transition-duration:.01ms !important}\n}\n`\n\n/** 短时间:今天只显示时分,其它显示月/日 时:分。 */\nfunction shortTime(input?: string | number | null): string {\n if (input === undefined || input === null || input === '') return ''\n const date = new Date(input)\n if (Number.isNaN(date.getTime())) return ''\n const now = new Date()\n const sameDay = date.toDateString() === now.toDateString()\n const hhmm = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n return sameDay ? hhmm : `${date.getMonth() + 1}/${date.getDate()} ${hhmm}`\n}\n\n/** 相对时间:刚刚 / N 分钟前 / N 小时前 / N 天前。 */\nfunction relTime(input?: string | null): string {\n if (!input) return ''\n const at = new Date(input).getTime()\n if (!Number.isFinite(at)) return ''\n const diff = Date.now() - at\n if (diff < 60_000) return '刚刚'\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`\n return `${Math.floor(diff / 86_400_000)} 天前`\n}\n\n/** 倒计时:还剩 2 小时 13 分 / 已解除。 */\nfunction countdown(untilMs?: number | null): string {\n if (!untilMs || !Number.isFinite(untilMs)) return ''\n const left = untilMs - Date.now()\n if (left <= 0) return '已解除'\n const totalMinutes = Math.ceil(left / 60_000)\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n if (hours > 0) return `剩余 ${hours} 小时 ${minutes} 分`\n return `剩余 ${minutes} 分`\n}\n\nfunction el(tag: string, cls?: string, text?: string): HTMLElement {\n const node = document.createElement(tag)\n if (cls) node.className = cls\n if (text !== undefined) node.textContent = text\n return node\n}\n\n/** 面板本体:命令式 DOM(生态既有插件同款做法),挂在 React 容器里。 */\nfunction Panel(): any {\n const hostRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n const root = hostRef.current\n if (!root) return\n let disposed = false\n let timer: number | undefined\n\n const style = document.createElement('style')\n style.textContent = styles\n\n const page = el('div', 'dsw-page')\n const title = el('h3', 'dsw-title', 'DeepSeek 网页登录(免费模型)')\n // 副标题的宽度是\"预算\"问题,不是随便写的:\n // 上一版(78 字)单行要 558px,而宿主面板的内容宽约 560px —— 正好压在折行边界上。\n // 「账号」页比「模型」页高,面板因此出现纵向滚动条,内容宽度少十几像素,\n // 最后一个词就掉到第二行,还断在 `deepseek-web` 的连字符处(看着像故障)。\n // 现在这版约 467px,留 ~90px 余量,切标签/缩放窗口都不会再翻行。\n // ⚠️ 以后改这句话,请保持单行宽度 ≲ 470px(量法:white-space:nowrap 的 span 取 getBoundingClientRect)。\n const sub = el('p', 'dsw-sub')\n sub.append('用 chat.deepseek.com 的登录态驱动 DSH,不需要 API Key(provider:')\n sub.append(el('span', 'dsw-nobreak', 'deepseek-web'))\n sub.append(')')\n\n page.append(style, title, sub)\n\n // ── 操作反馈:常驻在标签栏之上 ──────────────────────────────\n // 拆成标签页后,在「账号」页点按钮的反馈若落在别的页里就等于看不见,所以它不归属任何一页。\n const message = el('div', 'dsw-alert')\n message.style.display = 'none'\n page.append(message)\n\n const showMessage = (text: string, kind: 'ok' | 'err' | '' = ''): void => {\n message.textContent = text\n message.className = `dsw-alert${kind ? ` ${kind}` : ''}`\n message.style.display = 'block'\n }\n\n // ── 标签栏 + 四个页 ────────────────────────────────────────\n // 原来 7 张卡堆在一页,「找某一项」要滚很久。分组按\"什么时候会用它\":\n // 账号(登录/换号)· 模型(查阅与测试)· 防风控(限流与清理)· 传输层(指纹)\n const TAB_KEYS = ['account', 'model', 'gate', 'transport', 'context', 'about'] as const\n const TAB_LABELS: Record<string, string> = {\n account: '账号',\n model: '模型',\n gate: '防风控',\n transport: '传输层',\n context: '上下文',\n about: '关于',\n }\n const accountPane = el('div', 'dsw-pane')\n const modelPane = el('div', 'dsw-pane')\n const gatePane = el('div', 'dsw-pane')\n const transportPane = el('div', 'dsw-pane')\n const contextPane = el('div', 'dsw-pane')\n const aboutPane = el('div', 'dsw-pane')\n const panes: Record<string, HTMLElement> = {\n account: accountPane,\n model: modelPane,\n gate: gatePane,\n transport: transportPane,\n context: contextPane,\n about: aboutPane,\n }\n const tabButtons: Record<string, HTMLButtonElement> = {}\n const selectTab = (key: string): void => {\n for (const each of TAB_KEYS) {\n const on = each === key\n panes[each].hidden = !on\n tabButtons[each].classList.toggle('active', on)\n tabButtons[each].setAttribute('aria-selected', String(on))\n }\n }\n const tabBar = el('div', 'dsw-tabs')\n tabBar.setAttribute('role', 'tablist')\n for (const key of TAB_KEYS) {\n const btn = el('button', 'dsw-tab', TAB_LABELS[key]) as HTMLButtonElement\n btn.type = 'button'\n btn.setAttribute('role', 'tab')\n btn.addEventListener('click', () => selectTab(key))\n tabButtons[key] = btn\n tabBar.append(btn)\n panes[key].setAttribute('role', 'tabpanel')\n }\n page.append(tabBar, ...TAB_KEYS.map((key) => panes[key]))\n selectTab('account')\n\n // ── 账号页内部再分两个子页 ──────────────────────────────────────\n // 4 张卡堆在一页还是太长。按\"你是来看状态、还是来管账号\"分:\n // 登录状态 这个号现在能不能用 / 怎么再登一次\n // 账号库 我有哪些号 / 怎么加、切、删、导入导出\n // 子页签用**分段胶囊**,与一级的下划线标签在视觉上分层。\n const ACCT_KEYS = ['status', 'library'] as const\n const ACCT_LABELS: Record<string, string> = { status: '登录状态', library: '账号库' }\n const acctStatusPane = el('div', 'dsw-pane')\n const acctLibraryPane = el('div', 'dsw-pane')\n const acctPanes: Record<string, HTMLElement> = { status: acctStatusPane, library: acctLibraryPane }\n const acctButtons: Record<string, HTMLButtonElement> = {}\n const selectAcctTab = (key: string): void => {\n for (const each of ACCT_KEYS) {\n const on = each === key\n acctPanes[each].hidden = !on\n acctButtons[each].classList.toggle('active', on)\n acctButtons[each].setAttribute('aria-selected', String(on))\n }\n }\n const acctTabBar = el('div', 'dsw-subtabs')\n acctTabBar.setAttribute('role', 'tablist')\n for (const key of ACCT_KEYS) {\n const btn = el('button', 'dsw-subtab', ACCT_LABELS[key]) as HTMLButtonElement\n btn.type = 'button'\n btn.setAttribute('role', 'tab')\n btn.addEventListener('click', () => selectAcctTab(key))\n acctButtons[key] = btn\n acctTabBar.append(btn)\n acctPanes[key].setAttribute('role', 'tabpanel')\n }\n accountPane.append(acctTabBar, acctStatusPane, acctLibraryPane)\n // 和一级标签同一个原则:用 hidden 属性切,不靠 CSS 类 ——\n // 万一样式没加载,退化成\"两页都显示\"(难看但能用),而不是\"除了一页全空白\"。\n selectAcctTab('status')\n\n // ── 登录卡 ──\n const loginCard = el('div', 'dsw-card')\n const loginHead = el('div', 'dsw-cardhead')\n const loginTitle = el('div')\n loginTitle.append(el('span', 'name', '登录状态'))\n const badge = el('span', 'dsw-badge off', '⚪ 未登录')\n loginTitle.append(badge)\n loginHead.append(loginTitle)\n loginCard.append(loginHead)\n\n const statusKv = el('div', 'dsw-kv')\n loginCard.append(statusKv)\n\n // 账号级限制单独占一行 + 自己的倒计时。\n // 为什么要单独一行、还要挂定时器:这个状态**只在生成请求被拒时才能学到**\n // (受限期间 users/current 依然返回 200),所以不能指望\"刷新时顺便更新\",\n // 要让\"还剩多久\"一直显示着。\n let activeLimitUntilMs: number | null = null\n const limitRow = el('p', 'dsw-hint dsw-limit')\n limitRow.style.display = 'none'\n limitRow.style.marginTop = '8px'\n loginCard.append(limitRow)\n const paintLimit = (): void => {\n if (!activeLimitUntilMs) {\n limitRow.style.display = 'none'\n return\n }\n const text = countdown(activeLimitUntilMs)\n limitRow.style.display = ''\n limitRow.textContent =\n text === '已解除'\n ? `账号级限制已解除(${shortTime(activeLimitUntilMs)})。`\n : `⚠️ 账号级限制:${text}(${shortTime(activeLimitUntilMs)} 解除)—— 期间生成会被拒;只读调用(校验 / 探活)不受影响。`\n }\n\n // ── 账号卡(退出 / 换号)──\n // 为什么要单独一张卡:退出登录以前只作为一个按钮塞在「手动粘贴 Token」那张卡的角落里,\n // 用户根本找不到(实测反馈)。退出账号是高频操作,必须显眼、且要能换号。\n const accountCard = el('div', 'dsw-card')\n accountCard.append(el('div', 'dsw-cardhead', '当前账号'))\n const accountLine = el('div', 'dsw-kv')\n accountCard.append(accountLine)\n const accountActions = el('div', 'dsw-row')\n accountActions.style.marginTop = '8px'\n const logoutBtn = el('button', 'dsw-btn danger', '退出当前账号') as HTMLButtonElement\n const switchBtn = el('button', 'dsw-btn ghost', '退出并登录其它账号') as HTMLButtonElement\n accountActions.append(logoutBtn, switchBtn)\n accountCard.append(accountActions)\n const accountHint = el(\n 'p',\n 'dsw-hint',\n '⚠️ 「退出」= 把该账号从账号库移除,不是\"只登出\":本地凭证与浏览器登录态会一起清掉。' +\n '想留住它就先「导出备份」;只是想换个号用,去「账号库 → 登录新账号」。',\n )\n accountCard.append(accountHint)\n\n const loginActions = el('div', 'dsw-row')\n loginActions.style.marginTop = '10px'\n const browserBtn = el('button', 'dsw-btn', '浏览器窗口登录') as HTMLButtonElement\n const externalBtn = el('button', 'dsw-btn ghost', '用我的默认浏览器登录') as HTMLButtonElement\n const recoverBtn = el('button', 'dsw-btn ghost', '从已登录窗口恢复') as HTMLButtonElement\n const refreshBtn = el('button', 'dsw-btn ghost', '刷新状态') as HTMLButtonElement\n loginActions.append(browserBtn, externalBtn, recoverBtn, refreshBtn)\n loginCard.append(loginActions)\n loginCard.append(\n el(\n 'p',\n 'dsw-hint',\n '「用我的默认浏览器登录」= 用系统浏览器打开 chat.deepseek.com(网页端若提示「使用环境异常」,走这条)。' +\n '外部浏览器的登录态插件抓不到,所以要用 F12 控制台取 token 粘到下面那张卡(命令已备好)。',\n ),\n )\n loginCard.append(\n el(\n 'p',\n 'dsw-hint',\n '「从已登录窗口恢复」= 复用上次登录过的窗口分区直接取凭证(免重新登录),凭证丢失时用它救急。',\n ),\n )\n acctStatusPane.append(loginCard, accountCard)\n\n // ── 账号库:多账号并存 + 一键切换 ────────────────────────────────\n // 为什么值得有:以前换号的代价是「退出 → 清浏览器分区 → 重新登录 → 等捕获」,\n // 期间原来的号也回不去了。现在保存过的账号都在库里,切换即时生效。\n //\n // ⚠️ 风险已在 src/accounts.ts 的模块注释里写明,这里再对使用者说一遍:\n // 用多账号轮换规避单账号限流,会被服务商把账号关联起来,处置通常更重。\n // 所以刻意**只提供手动切换、不做自动轮换** —— 真人不会几分钟换一个号接着发。\n /**\n * 一句话反馈节点:给它的 `textContent` 赋值时**自动选样式** ——\n * 命中「失败/错误/无法」→ 红框红字(醒目),以「已…」开头 → 绿框,其余 → 灰色小字。\n *\n * 为什么自动判定、而不是让每个调用点传 kind(2026-09-15 用户反馈):\n * 「账号切换失败」原来和旁边的说明文字长得一模一样(都是灰色小字),根本注意不到。\n * 这三个节点加起来 40 多个赋值点,靠人手传 kind 迟早会漏 —— 而漏掉的那个往往正是报错。\n */\n const createMsgNode = (): { node: HTMLElement; textContent: string } => {\n const node = el('p', 'dsw-hint dsw-gate-msg', '')\n let text = ''\n return {\n node,\n get textContent(): string {\n return text\n },\n set textContent(value: string) {\n text = value ?? ''\n node.textContent = text\n node.className = /失败|错误|无法|不对|⚠️/.test(text)\n ? 'dsw-msg err'\n : /^(✅|已切换|已移除|已捕获|已保存|已原地)/.test(text)\n ? 'dsw-msg ok'\n : 'dsw-hint dsw-gate-msg'\n },\n }\n }\n\n const accountsCard = el('div', 'dsw-card')\n const accountsHead = el('div', 'dsw-cardhead')\n accountsHead.append(el('span', 'name', '账号库'))\n const accountsBadge = el('span', 'dsw-badge off', '—')\n accountsHead.append(accountsBadge)\n accountsCard.append(accountsHead)\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 保存过的账号都在本机,点「切换」即时生效、不用重新登录 —— 切换后从下一次请求开始生效。',\n ),\n )\n\n const accountsList = el('ul', 'dsw-accounts')\n accountsCard.append(accountsList)\n const accountsEmpty = el('p', 'dsw-hint', '账号库是空的 —— 用上面的「登录」或「手动粘贴 Token」添加一个。')\n accountsCard.append(accountsEmpty)\n\n const accountsIOPanel = el('div', 'dsw-row')\n accountsIOPanel.style.marginTop = '10px'\n // 「登录新账号」:以前账号库**根本没有\"再加一个号\"的入口** ——\n // 两个退出按钮都会先把这个号从库里删掉,所以库永远攒不到第二个账号。\n const addAccountBtn = el('button', 'dsw-btn', '登录新账号(添加)') as HTMLButtonElement\n const exportBtn = el('button', 'dsw-btn ghost', '导出备份…') as HTMLButtonElement\n const importBtn = el('button', 'dsw-btn ghost', '导入备份…') as HTMLButtonElement\n // 「校验全部」:对库里每个账号做一次**只读探活**(零额度)—— 自动探活 30 分钟才一次,\n // 而\"我刚在浏览器里动过这个号,它现在到底还行不行\"是随时会冒出来的问题。\n // 以前只能等,或者切过去试(那要发一次生成请求、烧额度)。\n //\n // ⚠️ 命名避开「刷新状态」:那个名字已经被「当前账号」卡片上的按钮占了\n //(它刷的是 `/status`,只看当前那个号)—— 两个同名按钮在同一个面板里会互相误导。\n const refreshAccountsBtn = el('button', 'dsw-btn ghost', '校验全部') as HTMLButtonElement\n refreshAccountsBtn.title = '对每个账号做一次只读校验(零额度):刷新登录态、补上账号名、清掉已恢复的失败标记'\n const newGroupBtn = el('button', 'dsw-btn ghost', '新建分组') as HTMLButtonElement\n newGroupBtn.title = '给账号分类,只影响列表的显示方式 —— 不参与切号、也不影响会话复用与清理'\n accountsIOPanel.append(addAccountBtn, exportBtn, importBtn, refreshAccountsBtn, newGroupBtn)\n accountsCard.append(accountsIOPanel)\n // 以前这里是个\"要导入的备份文件路径\"输入框 —— 让人手打路径本来就别扭。\n // 现在两个按钮都弹**系统对话框**(另存为 / 打开),位置和文件名由用户自己选。\n // 实现见 src/file-picker.ts(宿主是 utility 进程,拿不到 Electron 的 dialog,\n // 只能由渲染进程用 Chromium 自己的能力做)。\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 「登录新账号」会先清掉上次的浏览器登录态(库里已有的账号不受影响),登录后新账号只入库、不切换当前账号 ——' +\n '加完在列表里点「切换」即可使用。某个账号标着「需要重新登录」时,用它自己那行上的按钮修 ——' +\n '那条路不清浏览器登录态,能复用就直接复用。导出/导入会弹系统对话框,自己选位置和文件。',\n ),\n )\n const accountsMsg = createMsgNode()\n accountsCard.append(accountsMsg.node)\n accountsCard.append(\n el(\n 'p',\n 'dsw-hint',\n '⚠️ 导出的备份文件就是可完整登录的凭证(等同于账号本身)—— 别分享、别提交到仓库。',\n ),\n )\n acctLibraryPane.append(accountsCard)\n\n /**\n * 一个组的标题行:折叠箭头 + 组名 + 数量 + (真实组才有)改名 / 删除。\n *\n * 「未分组」是**伪组**(`groupId === null`),不给改名/删除入口 —— 它不是一个实体,\n * 只是\"没归组的账号\"的落脚处。\n */\n const sectionRow = (section: any, groups: any[]): HTMLElement => {\n const key = String(section?.key ?? '')\n const collapsed = collapsedGroups.has(key)\n const head = el('div', 'dsw-grouphead')\n const toggle = el('span', 'gtoggle', collapsed ? '▸' : '▾')\n toggle.title = collapsed ? '展开' : '折叠'\n toggle.addEventListener('click', () => {\n if (collapsedGroups.has(key)) collapsedGroups.delete(key)\n else collapsedGroups.add(key)\n persistCollapsedGroups()\n // 折叠是纯前端状态 ⇒ 直接重画,不用再打一次 /accounts(也不该走重建签名)\n redrawAccounts()\n })\n head.append(toggle, el('span', 'gname', String(section?.name ?? '')))\n head.append(el('span', 'gcount', `${Array.isArray(section?.accounts) ? section.accounts.length : 0} 个`))\n head.append(el('span', 'gspacer'))\n if (section?.groupId) {\n const renameGroupBtn = el('button', 'dsw-btn ghost', '改名') as HTMLButtonElement\n renameGroupBtn.addEventListener('click', () => {\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.value = String(section.name ?? '')\n input.placeholder = '分组名'\n head.append(input)\n input.focus()\n let settled = false\n const commit = (): void => {\n if (settled) return\n settled = true\n void renameAccountGroup(String(section.groupId), input.value)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') {\n settled = true\n redrawAccounts()\n }\n })\n input.addEventListener('blur', commit)\n })\n const deleteGroupBtn = el('button', 'dsw-btn danger', '删除') as HTMLButtonElement\n deleteGroupBtn.title = '只删分组本身;组里的账号会回到「未分组」,账号与凭证都不动'\n let armed = false\n deleteGroupBtn.addEventListener('click', () => {\n if (!armed) {\n armed = true\n deleteGroupBtn.textContent = '确认删除?'\n window.setTimeout(() => {\n armed = false\n deleteGroupBtn.textContent = '删除'\n }, 4000)\n return\n }\n void deleteAccountGroup(String(section.groupId))\n })\n head.append(renameGroupBtn, deleteGroupBtn)\n }\n return head\n }\n\n const renderAccounts = (data: any): void => {\n const items: any[] = Array.isArray(data?.accounts) ? data.accounts : []\n const groups: any[] = Array.isArray(data?.groups) ? data.groups : []\n const sections: any[] = Array.isArray(data?.sections) ? data.sections : []\n accountsBadge.textContent = `${items.length} 个`\n accountsBadge.className = `dsw-badge ${items.length ? 'on' : 'off'}`\n accountsEmpty.hidden = items.length > 0\n accountsList.textContent = ''\n // 兜底:万一拿到的是没有 sections 的载荷(老宿主 / 异常响应),退回单区平铺 ——\n // 宁可少个分组标题,也不能什么都看不到。\n if (!sections.length) {\n for (const item of items) accountsList.append(accountRow(item, groups, false))\n return\n }\n for (const section of sections) {\n accountsList.append(sectionRow(section, groups))\n if (collapsedGroups.has(String(section?.key ?? ''))) continue\n for (const item of Array.isArray(section?.accounts) ? section.accounts : []) {\n accountsList.append(accountRow(item, groups, true))\n }\n }\n }\n\n /**\n * 「重新登录这个账号」:给凭证失效(探活失败)的账号用。\n *\n * 与「登录新账号」的唯一区别是**不清浏览器登录态**:后者要先清干净才能加到别的号,\n * 而修同一个号正相反 —— 留着才可能一打开就复用上。落库仍走添加模式(只入库、不切换),\n * 所以修好它也不会顶掉你正在用的账号;若它本来就是当前账号,那正是你要的效果。\n */\n const reloginAccount = (id: string, title: string): void => {\n void (async () => {\n // 这里刻意不写死\"会复用登录态\" —— 宿主在「这条账号已被标记失效」时会走清理路径\n // (0.1.75),该说什么由它随响应返回(下面用 prep.hint),免得界面与实际行为对不上。\n accountsMsg.textContent = `正在为「${title}」打开登录窗口……`\n try {\n const prep = await api('/login/relogin', { method: 'POST', body: JSON.stringify({ id }) })\n if (prep?.ok === false) {\n accountsMsg.textContent = `准备失败:${prep?.error ?? '未知原因'}`\n return\n }\n boostUntil = Date.now() + 300_000\n accountsMsg.textContent = String(\n prep?.hint ??\n '登录窗口已打开:如果浏览器里还留着这个账号的登录态会立刻复用,否则在里面重新登录一次……',\n )\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n accountsMsg.textContent = `打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用「手动粘贴 Token」)`\n return\n }\n accountsMsg.textContent = result?.relogin\n ? `「${title}」的凭证已原地更新(同一条记录、当前账号未变),旧的失败标记也清掉了。`\n : result?.added\n ? `「${title}」已加入账号库 —— 当前使用的账号没有改变。`\n : '已捕获并保存凭证。'\n await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `重新登录失败:${error?.message ?? error}`\n } finally {\n await refresh(false).catch(() => undefined)\n }\n })()\n }\n\n const accountRow = (item: any, groups: any[] = [], indent = false): HTMLElement => {\n const row = el('li', `dsw-account${item.isActive ? ' active' : ''}${indent ? ' grouped' : ''}`)\n const main = el('div', 'dsw-account-main')\n const title = el('div', 'dsw-account-title')\n title.append(el('span', undefined, item.title || item.id))\n if (item.isActive) title.append(el('span', 'dsw-badge on', '✅ 当前'))\n if (item.unverified) title.append(el('span', 'dsw-badge off', '❔ 未校验'))\n const limited = item.limit && Number.isFinite(item.limit.untilMs) && item.limit.untilMs > Date.now()\n if (limited) {\n title.append(el('span', 'dsw-badge off', `⏳ 受限至 ${shortTime(item.limit.untilMs)}`))\n }\n // 徽章从「校验失败」改成**明确的行动指令**:原来只写\"失败\",用户不知道该干嘛,\n // 也看不出这号还能不能用。探活失败基本只有一种可能 —— 凭证失效,要重新登录。\n if (item.lastVerifyError) title.append(el('span', 'dsw-badge err', '❌ 需要重新登录'))\n main.append(title)\n\n const meta: string[] = []\n if (item.display) meta.push(item.display)\n if (item.capturedAt) meta.push(`${shortTime(item.capturedAt)} 捕获`)\n meta.push(item.lastVerifiedAt ? `最近校验 ${relTime(item.lastVerifiedAt)}` : '尚未校验')\n const cookieMeta: any[] = Array.isArray(item.cookieMeta) ? item.cookieMeta : []\n meta.push(`cookie:${describeCookieLife(summarizeCookieLife(cookieMeta))}`)\n main.append(el('div', 'dsw-account-meta', meta.join(' · ')))\n\n // 探活失败 → 不只报状态,给一条可执行的路径。\n // 为什么这里用「重新登录」而不是让用户自己点「登录新账号」:后者会**先清掉浏览器\n // 登录态**(因为它的目标是加一个*别的*号),而修同一个号正相反 —— 浏览器里可能还\n // 留着登录态,不清就能一打开直接复用,一个密码都不用敲。见宿主 /login/relogin。\n if (item.lastVerifyError) {\n // 只留一行说明(短、留在行内);修复按钮挪到右侧动作列 ——\n // 原来它独占一整行、又长又占地方(用户反馈:放到 切换/重命名/移除 那一列去)。\n main.append(\n el(\n 'div',\n 'dsw-account-fix',\n `⚠️ ${relTime(item.lastVerifyError.at)}校验失败:${item.lastVerifyError.message}`,\n ),\n )\n }\n row.append(main)\n\n const actions = el('div', 'dsw-account-actions')\n // 失效的账号:「重登」放动作列最前面 —— 它是这行最该点的按钮。\n // 文案从「重新登录」缩成「重登」:动作列现在要放下切换/备注/归组/移除,太长会挤成两行。\n if (item.lastVerifyError) {\n const reloginBtn = el('button', 'dsw-btn dsw-preset', '重登') as HTMLButtonElement\n reloginBtn.title = '能复用浏览器里的登录态就直接复用(不必敲密码);若这条账号已被标记失效,会先清掉登录态、让你重新登录一次。捕获后原地更新这条记录,不新增、也不切换当前账号'\n reloginBtn.addEventListener('click', () => reloginAccount(item.id, item.title || item.id))\n actions.append(reloginBtn)\n }\n if (item.isActive) {\n actions.append(el('span', 'dsw-hint', '使用中'))\n } else {\n const useBtn = el('button', 'dsw-btn ghost dsw-preset', '切换') as HTMLButtonElement\n useBtn.addEventListener('click', () => void switchToAccount(item.id))\n actions.append(useBtn)\n }\n\n // 文案叫「备注」而不是「重命名」:它写的是 `label`(备注名),**不是**改显示名 ——\n // 原来的名字让人以为是重命名账号本身,结果 7 个账号的 label 一直是空的(功能没人发现)。\n const renameBtn = el('button', 'dsw-btn ghost dsw-preset', '备注') as HTMLButtonElement\n let editing = false\n renameBtn.addEventListener('click', () => {\n if (editing) return\n editing = true\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.value = item.label || ''\n input.placeholder = '备注名,如「工作号」'\n main.append(input)\n input.focus()\n let settled = false\n const commit = (): void => {\n if (settled) return\n settled = true\n void saveAccountLabel(item.id, input.value)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') {\n settled = true\n void loadAccounts()\n }\n })\n input.addEventListener('blur', commit)\n })\n actions.append(renameBtn)\n\n // 归组用**下拉**而不是拖拽:这套面板是手写 DOM,拖拽的命中判定、与轮询重建的冲突\n // 都太容易出坑;下拉最少点击,而且天然幂等(\"设成某个值\"而不是\"挪到某个位置\")。\n const groupSel = el('select', 'dsw-groupsel') as HTMLSelectElement\n groupSel.title = '放进某个分组(只影响显示方式,不影响切号与会话复用)'\n groupSel.append(new Option('未分组', ''))\n for (const group of groups) groupSel.append(new Option(String(group?.name ?? ''), String(group?.id ?? '')))\n groupSel.value = String(item.groupId ?? '')\n groupSel.addEventListener('change', () => void assignAccountGroup(item.id, groupSel.value))\n actions.append(groupSel)\n\n // 移除要二次确认:凭证一旦删掉就找不回来了(不保留明文归档,见 accounts.ts)\n const removeBtn = el('button', 'dsw-btn danger dsw-preset', '移除') as HTMLButtonElement\n let armed = false\n removeBtn.addEventListener('click', () => {\n if (!armed) {\n armed = true\n removeBtn.textContent = '确认移除?'\n removeBtn.classList.add('armed')\n window.setTimeout(() => {\n armed = false\n removeBtn.textContent = '移除'\n removeBtn.classList.remove('armed')\n }, 4_000)\n return\n }\n void removeAccountById(item.id)\n })\n actions.append(removeBtn)\n row.append(actions)\n return row\n }\n\n /**\n * 渲染账号库,但**内容没变就不重建 DOM**。\n *\n * 为什么要有这道守卫:轮询会按秒级节拍重读 `/accounts`(见下面的 syncAccounts),\n * 无脑重建会把用户正在悬停/正要点的按钮换掉(重命名输入框更糟 —— 直接失焦)。\n * 签名取的是界面真正渲染的东西,见 account-sync.ts。\n */\n const applyAccounts = (data: any): void => {\n lastAccountsPayload = data\n const signature = accountsSignature(data)\n if (signature && signature === accountsSignatureCache) return\n accountsSignatureCache = signature\n renderAccounts(data)\n }\n\n /** 用最近一次载荷重画(折叠这类纯前端状态用它,不走重建签名、也不打 HTTP)。 */\n const redrawAccounts = (): void => {\n if (lastAccountsPayload) renderAccounts(lastAccountsPayload)\n }\n\n /** 显式读取(失败要说给用户听):初始化 / 切号 / 移除 / 添加 / 重新登录 / 导入之后用。 */\n const loadAccounts = async (): Promise<void> => {\n try {\n applyAccounts(await api('/accounts'))\n } catch (error: any) {\n accountsMsg.textContent = `账号库读取失败:${error?.message ?? error}`\n } finally {\n accountsSyncedAt = Date.now()\n }\n }\n\n /**\n * 后台同步账号库(静默;只在内容变了时重建列表)。\n *\n * 为什么必须有它:账号库列表**不在 `/status` 里**,登录捕获又是异步落地的\n * (CDP 那条路要等用户在浏览器里登录完;能开 Electron 窗口那条路更是\"开窗即返回\")\n * ⇒ 只靠上面几个显式调用,捕获晚一步就永远不显示,得关掉设置页再打开。\n *\n * 后台同步失败**不写进 accountsMsg** —— 那是给用户操作反馈用的,\n * 被一个后台轮询的偶发失败覆盖掉会更让人困惑(下一次成功就自然好了)。\n */\n const syncAccounts = async (): Promise<void> => {\n accountsSyncedAt = Date.now()\n try {\n applyAccounts(await api('/accounts'))\n } catch {\n // 静默:这是后台同步,失败不该覆盖用户正在看的那条消息\n }\n }\n\n /**\n * 分组动作:**只有显示方式会变,账号与凭证一律不动。**\n *\n * 每次操作后都 `loadAccounts()` 重读,而不是就地改 DOM:\n * 宿主的 `/accounts` 是唯一事实来源;重读既能拿到组的新状态,也会顺带走一遍内容签名 ——\n * 签名没变就不重建,正在输入的框不会被打断。\n */\n const groupApi = async (path: string, body: unknown, okText: string): Promise<void> => {\n try {\n const result = await api(path, { method: 'POST', body: JSON.stringify(body) })\n if (result?.ok === false) {\n accountsMsg.textContent = `分组操作失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = okText\n } catch (error: any) {\n accountsMsg.textContent = `分组操作失败:${error?.message ?? error}`\n } finally {\n await loadAccounts()\n }\n }\n\n const assignAccountGroup = (id: string, groupId: string): Promise<void> =>\n groupApi(\n '/accounts/group/assign',\n { id, groupId },\n groupId ? '已归组。' : '已移出分组(回到「未分组」)。',\n )\n\n const renameAccountGroup = (id: string, name: string): Promise<void> =>\n groupApi('/accounts/group/rename', { id, name }, `分组已改名为「${name.trim()}」。`)\n\n const deleteAccountGroup = (id: string): Promise<void> =>\n groupApi('/accounts/group/delete', { id }, '分组已删除;组里的账号回到「未分组」(账号本身没动)。')\n\n /** 「新建分组」的内联输入框(与「备注」同款交互:Enter 提交、Esc 取消、失焦也提交)。 */\n let newGroupInput: HTMLInputElement | null = null\n const promptNewGroup = (): void => {\n if (newGroupInput && newGroupInput.isConnected) {\n newGroupInput.focus()\n return\n }\n const input = el('input', 'dsw-labelinput') as HTMLInputElement\n input.placeholder = '分组名,如「工作号」'\n accountsIOPanel.append(input)\n input.focus()\n newGroupInput = input\n let settled = false\n const dismiss = (): void => {\n settled = true\n newGroupInput = null\n input.remove()\n }\n const commit = (): void => {\n if (settled) return\n const name = input.value\n dismiss()\n if (!name.trim()) return\n void groupApi('/accounts/group/create', { name }, `已创建分组「${name.trim()}」。`)\n }\n input.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') commit()\n if (event.key === 'Escape') dismiss()\n })\n input.addEventListener('blur', commit)\n }\n\n /**\n * 手动刷新账号状态:只读探活,零额度。\n *\n * 按钮会**禁用并改文案** —— 库里账号是串行校验的,7 个要十几秒;不置灰的话\n * 用户会以为没反应而反复点(宿主侧也有互斥,但界面先挡住更好)。\n */\n const refreshAccountStates = async (): Promise<void> => {\n refreshAccountsBtn.disabled = true\n const original = refreshAccountsBtn.textContent\n refreshAccountsBtn.textContent = '校验中…'\n accountsMsg.textContent = '正在逐个校验账号(只读、零额度,串行进行,请稍候)……'\n try {\n const result = await api('/accounts/refresh', { method: 'POST', body: '{}' })\n if (result?.ok === false) {\n accountsMsg.textContent = `刷新失败:${result?.error ?? '未知原因'}`\n return\n }\n const failed = Number(result?.failed ?? 0)\n accountsMsg.textContent =\n failed > 0\n ? `已校验 ${result?.checked ?? 0} 个账号:${result?.passed ?? 0} 个正常、${failed} 个需要重登(列表里已标出)。`\n : `已校验 ${result?.checked ?? 0} 个账号:全部正常。`\n } catch (error: any) {\n accountsMsg.textContent = `刷新失败:${error?.message ?? error}`\n } finally {\n refreshAccountsBtn.disabled = false\n refreshAccountsBtn.textContent = original\n // 探活会回写记录(补账号名、清失败标记)⇒ 让它立刻出现在列表里\n await refresh(false).catch(() => undefined)\n }\n }\n\n refreshAccountsBtn.addEventListener('click', () => void refreshAccountStates())\n newGroupBtn.addEventListener('click', () => promptNewGroup())\n\n const switchToAccount = async (id: string): Promise<void> => {\n accountsMsg.textContent = '切换中……'\n try {\n const result = await api('/accounts/switch', { method: 'POST', body: JSON.stringify({ id }) })\n if (!result?.ok) {\n accountsMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = '已切换(下一次请求生效)。正在刷新登录状态……'\n await loadAccounts()\n await refresh(false)\n accountsMsg.textContent = '已切换(下一次请求生效)'\n } catch (error: any) {\n accountsMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n const removeAccountById = async (id: string): Promise<void> => {\n try {\n const result = await api('/accounts/remove', { method: 'POST', body: JSON.stringify({ id }) })\n if (!result?.ok) {\n accountsMsg.textContent = `移除失败:${result?.error ?? '未知原因'}`\n return\n }\n accountsMsg.textContent = '已移除该账号(凭证已删除)'\n await loadAccounts()\n await refresh(true)\n } catch (error: any) {\n accountsMsg.textContent = `移除失败:${error?.message ?? error}`\n }\n }\n\n const saveAccountLabel = async (id: string, label: string): Promise<void> => {\n try {\n await api('/accounts/rename', { method: 'POST', body: JSON.stringify({ id, label }) })\n } catch {}\n await loadAccounts()\n }\n\n addAccountBtn.addEventListener('click', () => {\n void (async () => {\n addAccountBtn.disabled = true\n accountsMsg.textContent = '正在准备登录窗口(会清掉上次的浏览器登录态,不影响账号库里的账号)……'\n try {\n const prep = await api('/login/add', { method: 'POST', body: '{}' })\n if (prep?.ok === false) {\n accountsMsg.textContent = `准备失败:${prep?.error ?? '未知原因'}`\n return\n }\n // 登录窗口是\"打开后等用户操作\"的,这里会一直等到捕获到凭证或超时;\n // 期间保持轮询更密一点,万一捕获是异步落地的也能及时刷出来。\n boostUntil = Date.now() + 300_000\n accountsMsg.textContent = '登录窗口已打开:请在窗口里登录另一个账号(不要登当前这个,否则只是刷新凭证)……'\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n accountsMsg.textContent = `打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用「手动粘贴 Token」)`\n return\n }\n if (result?.added) {\n accountsMsg.textContent = result.created\n ? '已把新账号加入账号库 —— 当前账号没有改动,点列表里的「切换」才会用它。'\n : '这个账号本来就在库里(凭证已更新)—— 当前账号未改动。'\n } else {\n accountsMsg.textContent = '已捕获并保存凭证。'\n }\n await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `添加账号失败:${error?.message ?? error}`\n } finally {\n addAccountBtn.disabled = false\n await refresh(false).catch(() => undefined)\n }\n })()\n })\n\n exportBtn.addEventListener('click', () => {\n void (async () => {\n exportBtn.disabled = true\n accountsMsg.textContent = '导出中……'\n try {\n // 先弹系统「另存为」,内容在用户选完位置后再取(顺序见 file-picker.ts 的注释)。\n const outcome = await saveWithPicker(suggestedExportName(), async () => {\n const data = await api('/accounts/export-json', { method: 'POST', body: '{}' })\n if (!data?.ok) throw new Error(data?.error ?? '读取备份内容失败')\n const { ok: _ok, ...backup } = data\n return JSON.stringify(backup, null, 2)\n })\n\n if (outcome.kind === 'saved') {\n accountsMsg.textContent = `已保存:${outcome.name}(含明文凭证,请妥善保管)`\n } else if (outcome.kind === 'cancelled') {\n accountsMsg.textContent = '已取消导出。'\n } else {\n // 系统「另存为」不可用 → 回退到宿主写插件目录(功能不因此失效,只是不能选位置)\n const result = await api('/accounts/export', { method: 'POST', body: '{}' })\n const why = outcome.kind === 'unsupported' ? '当前环境不支持系统另存为' : `系统另存为失败(${outcome.reason})`\n accountsMsg.textContent = result?.ok\n ? `${why},已改为保存到插件目录:${result.path}`\n : `导出失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n accountsMsg.textContent = `导出失败:${error?.message ?? error}`\n } finally {\n exportBtn.disabled = false\n }\n })()\n })\n\n importBtn.addEventListener('click', () => {\n void (async () => {\n importBtn.disabled = true\n accountsMsg.textContent = '请选择备份文件……'\n try {\n const file = await pickJsonFile()\n if (!file) {\n accountsMsg.textContent = '已取消导入。'\n return\n }\n accountsMsg.textContent = `正在读取 ${file.name}……`\n const source = await readImportSource(file)\n if (source.kind === 'unreadable') {\n accountsMsg.textContent = `读取失败:${source.name} 不是有效的 JSON 备份(${source.reason})`\n return\n }\n // 拿到真实路径时只把**路径**交给宿主(宿主自己读文件,凭证不进 HTTP);\n // 拿不到才退回传内容 —— 兜底,免得宿主没注入路径桥时功能整体失效。\n const body = source.kind === 'path' ? { path: source.path } : { payload: source.payload }\n const result = await api('/accounts/import', { method: 'POST', body: JSON.stringify(body) })\n accountsMsg.textContent = result?.ok\n ? `导入完成(${source.name}):新增 ${result.imported} / 更新 ${result.updated} / 跳过 ${result.skipped}`\n : `导入失败:${result?.error ?? '未知原因'}`\n if (result?.ok) await loadAccounts()\n } catch (error: any) {\n accountsMsg.textContent = `导入失败:${error?.message ?? error}`\n } finally {\n importBtn.disabled = false\n }\n })()\n })\n\n // ── 手动 token 卡 ──\n const tokenCard = el('div', 'dsw-card')\n tokenCard.append(el('div', 'dsw-cardhead', '手动粘贴 Token(可选路径)'))\n tokenCard.append(\n el(\n 'p',\n 'dsw-hint',\n '在浏览器打开 chat.deepseek.com 并登录 → F12 控制台执行下面一行 → 把结果粘贴到输入框:',\n ),\n )\n const snippet = el('code', 'dsw-code', \"JSON.parse(localStorage.getItem('userToken')).value\")\n tokenCard.append(snippet)\n tokenCard.append(\n el(\n 'p',\n 'dsw-hint',\n '(新版网页端 token 存在 {\"value\": …} 包装里;若上面那行报错,改成 localStorage.getItem(\\'userToken\\') 直接复制整串,插件会自动解包)',\n ),\n )\n const tokenInput = el('textarea', 'dsw-area') as HTMLTextAreaElement\n tokenInput.placeholder = '粘贴 token(包装 JSON 或裸 token 都行;可选:下一行粘贴 Cookie,格式 name=value; name2=value2)'\n tokenCard.append(tokenInput)\n const tokenActions = el('div', 'dsw-row')\n tokenActions.style.marginTop = '8px'\n const tokenBtn = el('button', 'dsw-btn', '保存并验证') as HTMLButtonElement\n tokenActions.append(tokenBtn)\n tokenCard.append(tokenActions)\n acctLibraryPane.append(tokenCard)\n\n // ── 测试卡 ──\n const testCard = el('div', 'dsw-card')\n testCard.append(el('div', 'dsw-cardhead', '连通性测试'))\n testCard.append(el('p', 'dsw-hint', '直接经适配器发一次最小请求(会消耗一点网页端额度):'))\n const testActions = el('div', 'dsw-row')\n testActions.style.marginTop = '6px'\n const modelSelect = el('select', 'dsw-input') as HTMLSelectElement\n modelSelect.style.maxWidth = '220px'\n const testBtn = el('button', 'dsw-btn', '发送测试') as HTMLButtonElement\n testActions.append(modelSelect, testBtn)\n testCard.append(testActions)\n const testOut = el('div', 'dsw-msg')\n testOut.style.display = 'none'\n testCard.append(testOut)\n\n // ── 模型卡 ──\n const modelsCard = el('div', 'dsw-card')\n modelsCard.append(el('div', 'dsw-cardhead', '可用模型(网页免费)'))\n const modelsList = el('ul', 'dsw-models')\n modelsCard.append(modelsList)\n const modelsHint = el('p', 'dsw-hint', '')\n modelsCard.append(modelsHint)\n modelPane.append(modelsCard, testCard)\n\n // (操作反馈条与 showMessage 已提到页头、标签栏之上)\n\n let loggedIn = false\n let electron = false\n let windowOpen = false\n /** 上次同步账号库的时刻(0 = 还没读过;初始化那次 refresh 之前会先读一次)。 */\n let accountsSyncedAt = 0\n /** 上次渲染用的内容签名(见 account-sync.ts)。 */\n let accountsSignatureCache = ''\n /**\n * 最近一次 `/accounts` 载荷。\n *\n * 折叠是**纯前端状态**(不进账号库、也不该进重建签名),但切换折叠要重画列表 ——\n * 所以留一份载荷,让\"折叠\"能直接触发一次局部重绘,而不必多打一次 HTTP。\n */\n let lastAccountsPayload: any = null\n\n const COLLAPSE_KEY = 'dsw-accounts-collapsed-groups'\n\n /** 折叠的组 key。读一次就常驻,避免每行都去碰 localStorage。 */\n const collapsedGroups = new Set<string>((() => {\n try {\n const raw = window.localStorage.getItem(COLLAPSE_KEY)\n const parsed = raw ? JSON.parse(raw) : []\n return Array.isArray(parsed) ? parsed.map((item: unknown) => String(item)) : []\n } catch {\n // 隐私模式 / 存储被禁:折叠状态退化成\"本次会话不折叠\",不该让面板挂掉\n return []\n }\n })())\n\n const persistCollapsedGroups = (): void => {\n try {\n window.localStorage.setItem(COLLAPSE_KEY, JSON.stringify([...collapsedGroups]))\n } catch {\n /* 存不下就算了 —— 折叠只是显示偏好 */\n }\n }\n let probeIntervalMs = 0\n\n const renderKv = (rows: [string, string][]): void => {\n statusKv.textContent = ''\n for (const [key, value] of rows) {\n statusKv.append(el('div', 'k', key), el('div', undefined, value))\n }\n }\n\n /**\n * 上下文页的状态渲染。卡片在下面才建,这里用\"先声明后赋值\"的方式接上 ——\n * 直接引用下面的 const 会在时序上踩 TDZ(applyStatus 早于卡片构造)。\n */\n let renderContextStatus: ((mode: string, chain: any) => void) | undefined\n\n const applyStatus = (status: StatusPayload): void => {\n loggedIn = !!status.auth?.loggedIn\n electron = !!status.electron\n windowOpen = !!status.loginWindowOpen\n // 上下文投喂的实际状态(模式 + 链是否真的在跑)跟着状态轮询刷新。\n // ⚠️ 这两个字段在 /status 的 **config** 里 —— 0.1.62 写成了顶层,等于这段从来没生效\n // (面板不重开就永远停在打开那一刻的段数),0.1.63 修正。\n const cfg = status.config as { contextMode?: string; contextChain?: unknown } | undefined\n if (cfg?.contextMode) {\n renderContextStatus?.(String(cfg.contextMode), cfg.contextChain)\n }\n\n badge.textContent = loggedIn ? (status.auth.unverified ? '❔ 已捕获(未校验)' : '✅ 已登录') : '⚪ 未登录'\n badge.className = `dsw-badge ${loggedIn ? (status.auth.unverified ? 'off' : 'on') : 'off'}`\n if (loggedIn && !status.auth.unverified) {\n const valid = status.validation\n if (valid && !valid.ok) {\n badge.textContent = '❌ 登录态校验失败'\n badge.className = 'dsw-badge err'\n }\n }\n\n const rows: [string, string][] = []\n activeLimitUntilMs = Number.isFinite(status.auth?.limitUntilMs) ? Number(status.auth?.limitUntilMs) : null\n paintLimit()\n probeIntervalMs = Number(status.config?.probeIntervalMs ?? 0)\n renderVersion({ current: status.config?.version, probeIntervalMs })\n renderPaths(status.paths)\n rows.push(['适配器注册', (status.registeredProviders ?? []).includes(status.provider) ? `✅ ${status.provider} 已注册到 llm` : `⚠️ 未在 llm 中找到 ${status.provider}`])\n if (loggedIn) {\n rows.push(['账号', status.auth.display || '(未获取到账号信息)'])\n rows.push(['捕获时间', status.auth.capturedAt ? new Date(status.auth.capturedAt).toLocaleString() : '未知'])\n // 凭证来源与完整度:以前这里对缺失项写「未捕获(可能仍可用)」,读起来像风险提示,\n // 实际含义只是「你走的是手动粘贴 token 那条路」。实测(2026-09-11):\n // 只有 Bearer token、没有 cookie / x-hif-* 时,校验、PoW、真实生成全部通过 ——\n // 所以这里如实说明「缺什么」以及「已证实不影响使用」,而不是留一句模糊的警告。\n const manualTokenMode = !status.auth.hasCookie && !status.auth.hasFingerprint\n if (manualTokenMode) {\n rows.push(['凭证来源', '手动粘贴 token(实测:仅凭 Bearer token 即可完成校验/求解/生成)'])\n } else {\n rows.push(['凭证来源', '浏览器登录捕获(token + cookie + 指纹头,登录态通常更耐久)'])\n }\n rows.push([\n 'Cookie',\n status.auth.hasCookie\n ? '✅ 已捕获'\n : '未捕获 —— 手动 token 模式本就没有(已验证不影响请求;若日后频繁遇到 AUTH/40003,改用「浏览器登录」)',\n ])\n if (status.auth.hasCookie) {\n // 把\"登录态到底还能撑多久\"从完全不可观察变成至少能看一半。\n // ⚠️ 措辞必须诚实:实测真正鉴权用的是 token(只发 token 不带 cookie 能通过,\n // 只发 cookie 不带 token 直接被拒 40002),所以这里只是**浏览器侧的上界**,\n // 不能让人读成\"到这天就掉线\"。\n rows.push([\n 'Cookie 过期',\n `${describeCookieLife(status.auth.cookieLife)}(会话级 = 浏览器关掉就没了;这是浏览器侧的上界,不是登录态寿命)`,\n ])\n }\n rows.push(['指纹头', status.auth.hasFingerprint ? '✅ 已捕获(x-hif-* / x-client-*)' : '未捕获 —— 同上,已实测可用'])\n rows.push(['PoW WASM', status.auth.wasmHost || '默认地址'])\n rows.push(['token 长度', `${status.auth.tokenLength ?? 0} 字符`])\n if (status.validation) rows.push(['服务端校验', status.validation.ok ? '通过' : `失败:${status.validation.error ?? ''}`])\n\n // 主动探活(后台定时做的,与上面这次\"按需校验\"是两件事):\n // 目的就是**在任务跑到一半之前**发现登录态失效。\n const verifiedAt = status.auth?.lastVerifiedAt\n const verifyError = status.auth?.lastVerifyError\n if (verifyError) {\n rows.push([\n '登录态探活',\n `❌ ${relTime(verifyError.at)}失败:${verifyError.message}(可能已过期,建议重新登录)`,\n ])\n } else if (verifiedAt) {\n rows.push(['登录态探活', `✅ ${relTime(verifiedAt)}通过(后台定时校验,零额度)`])\n }\n // 让用户能确认「防风控」到底生效成什么样(值来自配置,改配置后重启生效)\n const interval = status.config?.minRequestIntervalMs\n if (interval !== undefined) {\n rows.push([\n '请求节流',\n status.config?.allowConcurrent\n ? `⚠️ 允许并发 · 间隔 ${interval}ms(并发生成有账号级限制风险,不建议)`\n : `串行(一次只发一条)· 间隔 ${interval}ms`,\n ])\n }\n } else {\n // 未登录时说明「能用哪条路登录」(不要笼统写成「非 Electron 环境」:\n // 2026-09-11 起宿主是 utility 进程,但真实浏览器登录是可用的)\n const available = status.loginCapability\n rows.push([\n '登录方式',\n available\n ? available.canOpenWindow\n ? '插件自开窗口(Electron 主进程,带指纹伪装)'\n : available.browser\n ? `用真实浏览器登录(${available.browser} + 调试协议,自动读取凭证)`\n : '未找到 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 token'\n : electron\n ? '可开浏览器窗口'\n : '请用「用我的默认浏览器登录」+ 手动粘贴 token',\n ])\n }\n if (status.lastLoginResult) {\n rows.push(['最近结果', `${status.lastLoginResult.message} · ${new Date(status.lastLoginResult.at).toLocaleTimeString()}`])\n }\n if (status.loginWindowOpen && status.loginProgress?.captured) {\n const captured = status.loginProgress.captured\n rows.push([\n '捕获进度',\n `token ${captured.token ? '✓' : '…'} / cookie ${captured.cookie ? '✓' : '…'} / 指纹 ${captured.fingerprint ? '✓' : '…'}`,\n ])\n }\n if (status.loginProgress?.lastError && status.loginWindowOpen) {\n rows.push(['窗口提示', status.loginProgress.lastError])\n }\n if (status.fingerprint && status.fingerprint.stripped.length > 0) {\n rows.push(['指纹清理', `已剔除 ${status.fingerprint.stripped.length} 个 Electron 头:${status.fingerprint.stripped.join(', ')}`])\n } else if (status.loginWindowOpen) {\n rows.push(['指纹清理', '窗口已打开(尚未命中需要清理的头)'])\n }\n if (status.fingerprint?.pageUa) {\n const bad = /electron/i.test(status.fingerprint.pageUa)\n rows.push(['页面看到 UA', `${bad ? '⚠️ 仍含 Electron:' : '✅ '}${status.fingerprint.pageUa}`])\n }\n if (status.fingerprint?.pageBrands?.length) {\n const dirty = status.fingerprint.pageBrands.filter((b) => /electron|dsh/i.test(b))\n rows.push(['页面品牌', `${dirty.length ? `⚠️ ${dirty.join(', ')}` : '✅ '}${status.fingerprint.pageBrands.join(', ')}`])\n }\n if (status.fingerprint?.pageWebdriver !== undefined) {\n rows.push(['webdriver', status.fingerprint.pageWebdriver ? '⚠️ true(自动化痕迹)' : '✅ false'])\n }\n // ⚠️ 0.1.82:这一段必须在 renderKv 之前 —— 此前 `renderKv(rows)` 在它上面,\n // 「宿主进程」这一行 push 进去时表格已经画完了 ⇒ **那行永远不显示**,\n // 而\"窗口打不开\"时它恰恰是唯一能说明原因的证据。\n // 登录能力自检(2026-09-11 事故:DSH 把插件宿主挪到 utility 进程后,窗口 API 没了)\n const capability = status.loginCapability\n if (capability) {\n const mode = capability.canOpenWindow\n ? '可开 Electron 窗口'\n : capability.browser\n ? `无窗口 API(${capability.processType} 进程)→ 用真实浏览器`\n : `无窗口 API(${capability.processType} 进程)且未找到 Edge/Chrome`\n rows.push(['宿主进程', `${capability.processType} · ${mode}`])\n }\n renderKv(rows)\n\n if (!browserBtn.dataset.busy) browserBtn.disabled = false\n if (capability) {\n browserBtn.textContent = capability.canOpenWindow ? '浏览器窗口登录' : capability.browser ? `用 ${capability.browser} 登录` : '浏览器窗口登录'\n browserBtn.disabled = !capability.canOpenWindow && !capability.browser\n browserBtn.title = capability.canOpenWindow\n ? '插件自己开窗口(带指纹伪装)'\n : capability.browser\n ? `拉起真实的 ${capability.browser}(独立 profile)完成登录,插件通过调试协议读取登录态`\n : '既不能开窗口也没找到 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 Token'\n } else {\n browserBtn.disabled = !electron\n browserBtn.textContent = windowOpen ? '登录窗口已打开' : '浏览器窗口登录'\n browserBtn.title = electron ? '' : '当前宿主无法开窗:请用「用我的默认浏览器登录」+ 手动粘贴 Token'\n }\n\n // 账号卡:显示当前账号 + 退出按钮可用性\n accountLine.textContent = ''\n if (loggedIn) {\n accountLine.append(el('div', 'k', '账号'), el('div', undefined, status.auth.display || '(未获取到账号信息)'))\n accountLine.append(\n el('div', 'k', '登录时间'),\n el('div', undefined, status.auth.capturedAt ? new Date(status.auth.capturedAt).toLocaleString() : '未知'),\n )\n } else {\n accountLine.append(el('div', 'k', '状态'), el('div', undefined, '未登录(没有可退出的账号)'))\n }\n logoutBtn.disabled = !loggedIn\n switchBtn.disabled = !loggedIn || !electron\n switchBtn.title = electron ? '' : '当前不是 Electron 桌面端:请先「退出当前账号」,再手动粘贴另一个账号的 token'\n\n // 模型下拉\n if (modelSelect.options.length !== status.models.length) {\n modelSelect.textContent = ''\n for (const model of status.models) {\n const option = document.createElement('option')\n option.value = model.id\n option.textContent = model.name\n modelSelect.append(option)\n }\n }\n // 模型列表\n modelsList.textContent = ''\n for (const model of status.models) {\n const item = el('li')\n item.append(el('div', 'name', model.name))\n const ctxLabel = model.contextWindow >= 1_048_576\n ? `${(model.contextWindow / 1_048_576).toFixed(0)}M`\n : `${Math.round(model.contextWindow / 1024)}K`\n item.append(el('div', 'id', `${model.id} · thinking ${model.thinking ? '开' : '关'} · 上下文 ${ctxLabel} token(标称)`))\n item.append(el('div', 'id', model.description))\n modelsList.append(item)\n }\n modelsHint.textContent =\n `两条是同一个「快速模式」的思考开关两档预设(也可在模型选择器的推理强度里切换)。` +\n `图片输入直接可用(走网页端文件上传通道)。每次调用会新建并删除临时会话;` +\n `prompt 字符上限 ${status.config?.maxPromptChars ?? 0}(服务端硬上限 2621440 字符,` +\n `另附件 token 预算 890880 —— 后者常被误读成「上下文窗口」)。`\n }\n\n const refresh = async (light = true): Promise<void> => {\n try {\n const status = await api(`/status${light ? '?light=1' : ''}`)\n if (disposed) return\n applyStatus(status)\n } catch (error: any) {\n showMessage(`状态读取失败:${error?.message ?? error}`, 'err')\n }\n }\n\n // ── 请求节流卡:并发开关 + 间隔区间 + 会话清理 ──\n // 这三项直接决定会不会被账号级限流(实测双窗口并发 6 分钟内被限制 1 天),\n // 所以不该只藏在配置文件里 —— 界面上直接可调,改完即时生效并落盘。\n const gateCard = el('div', 'dsw-card')\n gateCard.append(el('div', 'dsw-cardhead', '请求节流(防风控)'))\n\n const rangeInput = (): HTMLInputElement => {\n const input = el('input', 'dsw-range') as HTMLInputElement\n input.type = 'range'\n input.min = '0'\n input.max = '30000'\n input.step = '500'\n return input\n }\n\n const concRow = el('div', 'dsw-gate-row')\n const concLabel = el('label', 'dsw-switch')\n const concInput = el('input') as HTMLInputElement\n concInput.type = 'checkbox'\n const concTrack = el('span', 'dsw-switch-track')\n concLabel.append(concInput, concTrack, el('span', undefined, '允许并发(同一账号同时发两条)'))\n concRow.append(concLabel)\n gateCard.append(concRow)\n\n const minRow = el('div', 'dsw-gate-row')\n minRow.append(el('span', 'dsw-gate-label', '间隔下限'))\n const minRange = rangeInput()\n const minValue = el('span', 'dsw-gate-value', '—')\n minRow.append(minRange, minValue)\n gateCard.append(minRow)\n\n const maxRow = el('div', 'dsw-gate-row')\n maxRow.append(el('span', 'dsw-gate-label', '间隔上限'))\n const maxRange = rangeInput()\n const maxValue = el('span', 'dsw-gate-value', '—')\n maxRow.append(maxRange, maxValue)\n gateCard.append(maxRow)\n\n const intervalHint = el('p', 'dsw-hint', '')\n gateCard.append(intervalHint)\n\n const presetRow = el('div', 'dsw-gate-row')\n gateCard.append(presetRow)\n\n // ── prompt 上限(token 体量的总阀门)──────────────────────────\n // 为什么它比\"间隔\"更值得调:网页 API 无状态 → **每一轮都要把整段转写重发**,\n // 转写越长、单次请求越贵。实测同一个会话里单次输入估算从 9.7k token 涨到 293k\n // (180 轮累计约 2900 万),随后账号被限流。间隔只影响\"多久发一次\",这个才影响\"每次发多少\"。\n const promptRow = el('div', 'dsw-gate-row')\n promptRow.append(el('span', 'dsw-gate-label', 'prompt 上限'))\n const promptRange = el('input', 'dsw-range') as HTMLInputElement\n promptRange.type = 'range'\n promptRange.setAttribute('aria-label', 'prompt 字符上限')\n promptRange.title = '每次请求最多发送多少字符(含工具目录与全部历史)'\n const promptValue = el('span', 'dsw-gate-value', '—')\n promptRow.append(promptRange, promptValue)\n gateCard.append(promptRow)\n const promptHint = el('p', 'dsw-hint', '')\n gateCard.append(promptHint)\n\n /** 字符数 → 「万字符」。界面上出现一长串 0 没人看得出差别。 */\n const fmtChars = (n: number): string => `${Number.isFinite(n) ? (n / 10_000).toFixed(n % 10_000 === 0 ? 0 : 1) : '—'} 万字符`\n\n const paintPromptCap = (): void => {\n const chars = Number(promptRange.value)\n promptValue.textContent = fmtChars(chars)\n const level = chars >= 1_200_000 ? '偏高' : chars >= 600_000 ? '中等' : '保守'\n promptHint.textContent =\n `每次请求最多发送 ${fmtChars(chars)}(${level})。` +\n '网页端是无状态的,每一轮都会把整段对话历史重新发一遍:' +\n '上限越大,模型越不容易「忘事」,但单次消耗的 token 也越多。' +\n '长任务建议调小,或拆成多个会话(新会话从零开始,单次最省)。'\n }\n // 把「底部那两个数字到底怎么来的」直接写在界面上:\n // 否则「缓存命中 0%」会被当成\"真的没命中\",而它其实只是我们没有上报。\n const tokenNote = el('p', 'dsw-hint', '')\n tokenNote.textContent =\n '💡 说明:DSH 底部的 token 数目前是按字符估算的(网页端只回一个「本消息累计 token」,' +\n '我们还没接进来);「缓存命中 0%」是因为网页端不提供缓存信息、我们也就没有上报 —— ' +\n '不代表真的没命中。'\n gateCard.append(tokenNote)\n\n promptRange.addEventListener('input', paintPromptCap)\n promptRange.addEventListener('change', () => {\n paintPromptCap()\n void saveGate({ maxPromptChars: Number(promptRange.value) })\n })\n\n // ── 图片数量上限(另一个请求级阀门)──────────────────────────\n // 为什么需要:图片是**请求级**的(一次请求用一个 ref_file_ids 带一批),网页端对这一批\n // 有数量上限 —— 群友实测 40 张通过 / 52 张被拒。超了会以 biz_code 10 拒收**整轮**,\n // 而图还留在历史里 ⇒ **该会话此后每一轮都失败**,用户只能丢掉全部上下文。\n // 所以按时间只带最近的 N 张,更早的略过。\n const imgRow = el('div', 'dsw-gate-row')\n imgRow.append(el('span', 'dsw-gate-label', '图片上限'))\n const imgRange = el('input', 'dsw-range') as HTMLInputElement\n imgRange.type = 'range'\n imgRange.setAttribute('aria-label', '单次请求的图片数量上限')\n imgRange.title = '一次请求最多带多少张历史图片(0 = 不限制)'\n const imgValue = el('span', 'dsw-gate-value', '—')\n imgRow.append(imgRange, imgValue)\n gateCard.append(imgRow)\n const imgHint = el('p', 'dsw-hint', '')\n gateCard.append(imgHint)\n\n const paintImageCap = (): void => {\n const count = Number(imgRange.value)\n imgValue.textContent = count === 0 ? '不限制' : `${count} 张`\n imgHint.textContent =\n count === 0\n ? '不限制(不推荐):历史里不同图片数超过 40 张左右后,网页端会以 code 10 拒绝整轮,且该会话此后每轮都失败。'\n : `一次请求最多带最近的 ${count} 张图片,更早的在本次请求里略过 —— 这是正常的长度控制,不是错误。` +\n '网页端能引用的图片数上限实测在 40~52 之间,默认值留了足够余量。'\n }\n imgRange.addEventListener('input', paintImageCap)\n imgRange.addEventListener('change', () => {\n paintImageCap()\n void saveGate({ maxRefImages: Number(imgRange.value) })\n })\n\n const cleanupRow = el('div', 'dsw-gate-row')\n cleanupRow.append(el('span', 'dsw-gate-label', '会话清理'))\n const cleanupBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['immediate', '立即'],\n ['deferred', '延迟(推荐)'],\n ['keep', '不删'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveGate({ sessionCleanup: key }))\n cleanupBtns[key] = btn\n cleanupRow.append(btn)\n }\n gateCard.append(cleanupRow)\n const cleanupHint = el('p', 'dsw-hint', '')\n gateCard.append(cleanupHint)\n\n // ── 会话清理的三个区间(只在「延迟」模式显示)──────────────────────\n // 为什么这三个参数也要\"上下限 + 随机\":它们原来都是**死值** —— 攒到第 8 个就动手、\n // 正好等 90 秒、逐个删除时请求连发。固定值方差≈0,本身就是机器特征;真人不会这么精确。\n // 所以每个参数给一对上下限,实际取值在区间内**随机抽**(阈值/等待每轮重抽,删除间隔每次重抽)。\n const cleanupRanges = el('div')\n cleanupRanges.style.display = 'none'\n gateCard.append(cleanupRanges)\n\n /** 一对「上下限」滑块:一行两个 range + 右侧合成值。拖动中只更新数字,松手才提交。 */\n const rangePair = (\n label: string,\n bounds: { min: number; max: number },\n step: string,\n format: (lo: number, hi: number) => string,\n commit: (lo: number, hi: number) => void,\n ): { row: HTMLElement; set: (lo: number, hi: number) => void } => {\n const row = el('div', 'dsw-gate-row')\n row.append(el('span', 'dsw-gate-label', label))\n const lo = el('input', 'dsw-range') as HTMLInputElement\n const hi = el('input', 'dsw-range') as HTMLInputElement\n for (const [input, which] of [[lo, '下限'], [hi, '上限']] as const) {\n input.type = 'range'\n input.min = String(bounds.min)\n input.max = String(bounds.max)\n input.step = step\n input.setAttribute('aria-label', `${label}${which}`)\n input.title = `${label}${which}`\n }\n const value = el('span', 'dsw-gate-pair-value', '—')\n const paint = (): void => {\n value.textContent = format(Number(lo.value), Number(hi.value))\n }\n const onInput = (): void => {\n // 两个滑块允许被拖反 —— 当场收紧成「下限 <= 上限」。\n // 后端也会纠正,但界面先纠更直观:总比\"拖完看着不对、过一会儿自己跳回去\"好。\n if (Number(lo.value) > Number(hi.value)) {\n const swap = lo.value\n lo.value = hi.value\n hi.value = swap\n }\n paint()\n }\n const onCommit = (): void => commit(Number(lo.value), Number(hi.value))\n lo.addEventListener('input', onInput)\n hi.addEventListener('input', onInput)\n lo.addEventListener('change', onCommit)\n hi.addEventListener('change', onCommit)\n const pair = el('div', 'dsw-range-pair')\n pair.append(lo, hi)\n row.append(pair, value)\n return {\n row,\n set: (lo2, hi2) => {\n lo.value = String(lo2)\n hi.value = String(hi2)\n paint()\n },\n }\n }\n\n const batchPair = rangePair(\n '攒够数量',\n { min: 1, max: 50 },\n '1',\n (lo, hi) => `${lo}~${hi} 个`,\n (lo, hi) => void saveGate({ cleanupBatch: { min: lo, max: hi } }),\n )\n const delayPair = rangePair(\n '最长等待',\n { min: 5, max: 600 },\n '5',\n (lo, hi) => `${lo}~${hi} 秒`,\n (lo, hi) => void saveGate({ cleanupDelayMs: { min: Math.round(lo * 1000), max: Math.round(hi * 1000) } }),\n )\n const gapPair = rangePair(\n '删除间隔',\n { min: 0, max: 60 },\n '0.1',\n (lo, hi) => (hi <= 0 ? '不等待' : `${lo.toFixed(1)}~${hi.toFixed(1)} 秒`),\n (lo, hi) => void saveGate({ cleanupGapMs: { min: Math.round(lo * 1000), max: Math.round(hi * 1000) } }),\n )\n cleanupRanges.append(batchPair.row, delayPair.row, gapPair.row)\n cleanupRanges.append(\n el(\n 'p',\n 'dsw-hint',\n '上面三个都是「下限 ~ 上限」:实际取值每次在区间内随机抽 —— 攒批阈值与最长等待每轮清理重抽、' +\n '删除间隔每删一个重抽。这样\"什么时候动手删\"就没有固定规律了。',\n ),\n )\n cleanupRanges.append(\n el(\n 'p',\n 'dsw-hint',\n '「删除间隔」只在逐个删除时起作用:优先走一个请求批量删;若服务端不接受批量删(会自动退化为逐个删),' +\n '相邻两个删除请求之间就按这个间隔停一下 —— 避免\"一下子连发几十个删除请求\"。',\n ),\n )\n\n gateCard.append(\n el(\n 'p',\n 'dsw-hint',\n '并发默认关闭:DSH 的会话标题生成会与主回答同时发往同一账号,网页端同一账号同时只能生成一条,' +\n '实测双窗口并发不到 6 分钟即触发账号级限制(1 天)。',\n ),\n )\n const gateMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n gateCard.append(gateMsg)\n gatePane.append(gateCard)\n\n // ── 调用台账:请求密度 + 失败分类 ──────────────────────────────\n // 节流\"到底有没有生效\"不能靠感觉 —— 看两个数:相邻对话的间隔分布(最短间隔说明有没有连环请求)、\n // 以及失败分类(限流 / 账号被限制 / 鉴权 / 网络各占多少)。\n const ledgerCard = el('div', 'dsw-card')\n const ledgerHead = el('div', 'dsw-cardhead')\n ledgerHead.append(el('span', 'name', '调用台账'))\n const ledgerBadge = el('span', 'dsw-badge off', '近 24 小时')\n ledgerHead.append(ledgerBadge)\n ledgerCard.append(ledgerHead)\n ledgerCard.append(\n el('p', 'dsw-hint', '本地记录每次调用的结果(不含任何对话内容与凭证)。用来判断节流是否真的在起作用。'),\n )\n const ledgerKv = el('div', 'dsw-kv')\n ledgerCard.append(ledgerKv)\n const ledgerSparkBox = el('div')\n ledgerCard.append(ledgerSparkBox)\n const ledgerRow = el('div', 'dsw-row')\n const ledgerRefreshBtn = el('button', 'dsw-btn ghost', '刷新台账') as HTMLButtonElement\n ledgerRow.append(ledgerRefreshBtn)\n ledgerCard.append(ledgerRow)\n const ledgerMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n ledgerCard.append(ledgerMsg)\n gatePane.append(ledgerCard)\n\n /** 24 根小柱:每小时调用量。高度按最大值归一,失败多的时段用红色。 */\n const sparkSvg = (hourly: number[], failedHours: Set<number>): string => {\n const values = hourly.length ? hourly : [0]\n const max = Math.max(1, ...values)\n const barW = 100 / values.length\n let out = '<svg class=\"dsw-spark\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">'\n values.forEach((value, index) => {\n const height = (value / max) * 100\n // 基础填充由 .dsw-spark rect 给;只有\"有失败\"的时段才额外挂 class(CSS 里 .bad 覆盖填充色)\n const cls = failedHours.has(index) ? ' class=\"bad\"' : ''\n out += `<rect${cls} x=\"${(index * barW).toFixed(2)}\" y=\"${(100 - height).toFixed(2)}\" width=\"${(barW * 0.7).toFixed(2)}\" height=\"${Math.max(height, value > 0 ? 3 : 0.6).toFixed(2)}\" rx=\"1\"></rect>`\n })\n out += '</svg>'\n return out\n }\n\n const fmtMs = (ms: number): string => (ms >= 60_000 ? `${(ms / 60_000).toFixed(1)} 分` : `${(ms / 1000).toFixed(1)} 秒`)\n\n const renderLedger = (data: any): void => {\n const calls = Number(data?.calls ?? 0)\n ledgerBadge.textContent = `近 ${data?.hours ?? 24} 小时`\n ledgerBadge.className = `dsw-badge ${calls > 0 ? 'on' : 'off'}`\n ledgerKv.textContent = ''\n ledgerSparkBox.textContent = ''\n if (calls === 0) {\n ledgerMsg.textContent = '还没有记录 —— 跑一次对话后回来看。'\n return\n }\n const push = (key: string, value: string): void => {\n ledgerKv.append(el('div', 'k', key), el('div', undefined, value))\n }\n push('调用次数', `${calls}(成功 ${data.succeeded ?? 0} / 失败 ${data.failed ?? 0})`)\n const gaps = data.gaps\n push(\n '相邻对话间隔',\n gaps\n ? `中位 ${fmtMs(gaps.p50)} · p90 ${fmtMs(gaps.p90)} · 最短 ${fmtMs(gaps.min)}(样本 ${gaps.samples})`\n : '样本不足',\n )\n if (gaps && gaps.min < 1_500) {\n ledgerMsg.textContent = `⚠️ 出现过 ${fmtMs(gaps.min)} 的极短间隔 —— 检查一下节流是否被关掉了。`\n }\n const failures = data.failures ?? {}\n push(\n '失败分类',\n Object.keys(failures).length ? Object.entries(failures).map(([key, count]) => `${key} ${count}`).join(' · ') : '无',\n )\n push('台账占用', `${data.footprint?.files ?? 0} 个文件 · ${Math.round((data.footprint?.bytes ?? 0) / 1024)} KB`)\n // F3(0.2.0):按账号用量 —— 多账号场景\"哪个号在烧钱/在被限\"一目了然。\n const byAccount: any[] = Array.isArray(data.byAccount) ? data.byAccount : []\n if (byAccount.length > 0) {\n const nameOf = (id: string): string => {\n const items: any[] = Array.isArray(lastAccountsPayload?.accounts) ? lastAccountsPayload.accounts : []\n const hit = items.find((item) => item.id === id)\n return hit ? String(hit.label || hit.display || hit.id) : id\n }\n push(\n '按账号',\n byAccount\n .sort((a, b) => (Number(b?.calls) || 0) - (Number(a?.calls) || 0))\n .map((row) => `${nameOf(row.accountId)} ${row.calls} 次(失败 ${row.failed})`)\n .join(' · '),\n )\n }\n const hourly: number[] = Array.isArray(data.hourly) ? data.hourly : []\n // 0.1.82:失败时段此前恒传空集 ⇒ `.bad` 是死样式、\"标红\"从未生效\n const failedHours = new Set<number>()\n const hourlyFailed: number[] = Array.isArray(data.hourlyFailed) ? data.hourlyFailed : []\n hourlyFailed.forEach((count, index) => {\n if (count > 0) failedHours.add(index)\n })\n ledgerSparkBox.innerHTML = sparkSvg(hourly, failedHours)\n ledgerSparkBox.append(el('p', 'dsw-hint', `每小时调用量(最近 1 小时在最右,峰值 ${Math.max(0, ...hourly)} 次)`))\n }\n\n const loadLedger = async (): Promise<void> => {\n try {\n renderLedger(await api('/ledger?hours=24'))\n } catch (error: any) {\n ledgerMsg.textContent = `台账读取失败:${error?.message ?? error}`\n }\n }\n ledgerRefreshBtn.addEventListener('click', () => void loadLedger())\n\n // ── 传输层卡:请求从哪个网络栈出去(决定 TLS/HTTP2 指纹像不像浏览器)──\n // 实测:Node fetch 的 JA4 是 `t13d…h1`(不走 HTTP/2、cipher 数量差 3 倍多、不带 GREASE);\n // 换成 Chromium 网络栈后 cipher 列表哈希与 Chrome **逐字节一致**。所以默认走 Chromium。\n // 但要留开关:Chromium 会跟随**系统代理**(Node 完全无视代理),梯子关着时可能反而连不上。\n const transportCard = el('div', 'dsw-card')\n transportCard.append(el('div', 'dsw-cardhead', '传输层(指纹)'))\n\n const transportRow = el('div', 'dsw-gate-row')\n transportRow.append(el('span', 'dsw-gate-label', '请求从哪出去'))\n const transportBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['chromium', 'Chromium 网络栈(推荐)'],\n ['node', 'Node'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveTransport(key))\n transportBtns[key] = btn\n transportRow.append(btn)\n }\n transportCard.append(transportRow)\n\n const transportHint = el('p', 'dsw-hint', '')\n transportCard.append(transportHint)\n const transportProxyHint = el('p', 'dsw-hint', '')\n transportCard.append(transportProxyHint)\n\n const testRow = el('div', 'dsw-gate-row')\n const transportTestBtn = el('button', 'dsw-btn ghost', '测试传输层(零额度)') as HTMLButtonElement\n testRow.append(transportTestBtn)\n transportCard.append(testRow)\n\n const transportTestOut = el('div', 'dsw-msg', '')\n transportTestOut.style.display = 'none'\n transportCard.append(transportTestOut)\n\n const transportMsg = createMsgNode()\n transportCard.append(transportMsg.node)\n transportPane.append(transportCard)\n\n // ── 上下文页:每轮发全量 prompt,还是只发增量 + 父消息链 ────────────\n // 背景(2026-09-14,读参考实现 + 抓真实帧得出):插件一直发 parent_message_id:null,\n // 每条消息都是会话里的根消息、没有父链,服务端回溯上下文到空 —— 所以历史只能每轮重发。\n // 浏览器不是这么干的(只有会话第一条 parent 是 null),链式投喂就是照它做:\n // 只发增量、把上一条回答挂成父消息。代价是工具协议只存在于链首那条消息里,\n // 一旦服务端把早期上下文丢掉,模型可能不按约定格式发工具调用 —— 所以默认仍是全量。\n const contextCard = el('div', 'dsw-card')\n contextCard.append(el('div', 'dsw-cardhead', '上下文投喂方式'))\n const contextRow = el('div', 'dsw-gate-row')\n contextRow.append(el('span', 'dsw-gate-label', '每轮发什么'))\n const contextBtns: Record<string, HTMLButtonElement> = {}\n for (const pair of [\n ['full', '每轮全量(默认)'],\n ['chained', '链式投喂(只发增量)'],\n ] as const) {\n const key = pair[0]\n const btn = el('button', 'dsw-btn ghost dsw-preset', pair[1]) as HTMLButtonElement\n btn.addEventListener('click', () => void saveContextMode(key))\n contextBtns[key] = btn\n contextRow.append(btn)\n }\n contextCard.append(contextRow)\n const contextStatus = el('p', 'dsw-hint', '')\n contextCard.append(contextStatus)\n const contextHintText = el('p', 'dsw-hint', '')\n contextCard.append(contextHintText)\n const contextMsg = createMsgNode()\n contextCard.append(contextMsg.node)\n contextPane.append(contextCard)\n\n renderContextStatus = (rawMode: string, chain: any): void => {\n const mode = rawMode === 'chained' ? 'chained' : 'full'\n for (const key of Object.keys(contextBtns)) {\n contextBtns[key].classList.toggle('active', key === mode)\n }\n if (chain) {\n contextStatus.textContent =\n `链式投喂正在跑:网页端会话 ${String(chain.sessionId ?? '').slice(0, 8)},` +\n `链上已发 ${chain.turns} 段,父消息 ${chain.parentId}`\n } else {\n contextStatus.textContent =\n mode === 'chained'\n ? '链式投喂:下一条消息会重新起链(当前没有可续的链,或还没开始用)'\n : '当前:每轮重发全量 prompt(和以前完全一致)'\n }\n }\n\n const applyContextCard = (info: any): void => {\n renderContextStatus?.(String(info?.mode ?? 'full'), info?.chain)\n const hint = String(info?.hint ?? '')\n // 设置文件路径单独一行 —— 想手工改配置的人需要它\n const path = String(info?.settingsPath ?? '')\n contextHintText.textContent = path ? `${hint}\\n配置文件:${path}` : hint\n }\n\n const saveContextMode = async (mode: 'full' | 'chained'): Promise<void> => {\n contextMsg.textContent = '切换中……'\n try {\n const result = await api('/context-mode', { method: 'POST', body: JSON.stringify({ mode }) })\n if (result?.ok) {\n applyContextCard(result)\n contextMsg.textContent =\n `已切到${result.mode === 'chained' ? '链式投喂' : '每轮全量'},即时生效` +\n (result.persisted === false\n ? '(未能写入配置,重启后会回到上次保存的值)'\n : '(已写入配置,重启后仍生效)')\n } else {\n contextMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n contextMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n // ── 关于页:版本与更新 / 数据位置 / 风险提示 ────────────────────\n // 单独一个标签而不是塞进别的页:这三块都是\"偶尔看一眼\"的信息,\n // 混进日常操作的页里只会稀释注意力。\n const aboutCard = el('div', 'dsw-card')\n aboutCard.append(el('div', 'dsw-cardhead', '版本与更新'))\n const versionKv = el('div', 'dsw-kv')\n aboutCard.append(versionKv)\n const updateRow = el('div', 'dsw-row')\n updateRow.style.marginTop = '8px'\n const updateBtn = el('button', 'dsw-btn ghost', '检查更新') as HTMLButtonElement\n updateRow.append(updateBtn)\n aboutCard.append(updateRow)\n const updateMsg = el('p', 'dsw-hint dsw-gate-msg', '')\n aboutCard.append(updateMsg)\n aboutCard.append(\n el('p', 'dsw-hint', '插件装不了包,所以这里只做\"检查 + 给链接\"。GitHub 在国内可能连不上,检查失败是正常的。'),\n )\n aboutPane.append(aboutCard)\n\n const renderVersion = (info: any): void => {\n versionKv.textContent = ''\n versionKv.append(el('div', 'k', '当前版本'), el('div', undefined, info?.current || '未知'))\n versionKv.append(\n el('div', 'k', '登录态探活'),\n el(\n 'div',\n undefined,\n info?.probeIntervalMs > 0\n ? `每 ${Math.round(info.probeIntervalMs / 60_000)} 分钟一次(只读、零额度)`\n : '已关闭',\n ),\n )\n }\n\n updateBtn.addEventListener('click', () => {\n void (async () => {\n updateBtn.disabled = true\n updateMsg.textContent = '正在检查……'\n try {\n const result = await api('/update-check', { method: 'POST', body: '{}' })\n if (!result?.ok) {\n updateMsg.textContent = `检查失败:${result?.error ?? '未知原因'}`\n return\n }\n const current = result.current\n renderVersion({ current, probeIntervalMs })\n if (result.hasUpdate) {\n updateMsg.textContent = `发现新版本 ${result.latest}(当前 ${current})${\n result.publishedAt ? ` · ${shortTime(result.publishedAt)} 发布` : ''\n }${result.url ? ` · ${result.url}` : ''}`\n } else {\n updateMsg.textContent = `已是最新版本(${current})。`\n }\n } catch (error: any) {\n updateMsg.textContent = `检查失败:${error?.message ?? error}`\n } finally {\n updateBtn.disabled = false\n }\n })()\n })\n\n const pathsCard = el('div', 'dsw-card')\n pathsCard.append(el('div', 'dsw-cardhead', '数据位置'))\n pathsCard.append(\n el('p', 'dsw-hint', '插件的本地状态都在 DSH 主目录下,不进通用配置面(避免凭证混进 settings/credentials)。'),\n )\n const pathsKv = el('div', 'dsw-kv')\n pathsCard.append(pathsKv)\n pathsCard.append(\n el('p', 'dsw-hint', '⚠️ 账号库里每个文件都是可完整登录的凭证;导出的备份同样是明文 —— 账号库卡片里的提示请当真。'),\n )\n aboutPane.append(pathsCard)\n\n const renderPaths = (paths: any): void => {\n pathsKv.textContent = ''\n const rows: [string, string][] = [\n ['账号库', paths?.accounts || '(未知)'],\n ['账号索引', paths?.webLogin ? `${paths.webLogin}/accounts.json` : '(未知)'],\n ['调用台账', paths?.ledger || '(未知)'],\n ['导出备份', paths?.webLogin ? `${paths.webLogin}/exports/` : '(未知)'],\n ]\n for (const [key, value] of rows) {\n pathsKv.append(el('div', 'k', key), el('div', 'dsw-path', value))\n }\n }\n\n const riskCard = el('div', 'dsw-card')\n riskCard.append(el('div', 'dsw-cardhead', '为什么没有「自动换号」'))\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '💡 账号库支持一键手动切换,但刻意不做自动轮换(检测到限流就自动换一个号继续发)。原因不是保守:',\n ),\n )\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '参考项目切的是「CLI 下次启动用哪个账号」——服务端看不到;而我们每次对话都实时发请求。' +\n '真人不会在几分钟内换一个账号接着发消息,自动换号是极强的机器行为特征,' +\n '与本插件在传输层指纹、随机间隔、会话清理上「降低机器可识别性」的努力直接冲突。',\n ),\n )\n riskCard.append(\n el(\n 'p',\n 'dsw-hint',\n '另外:同一服务商会把多账号关联起来(同设备 / 同 IP / 同指纹 / 相近的行为模式)。' +\n '一旦被判定为同一人的多开小号,处置通常比单账号超频更重,而且可能波及全部关联账号。' +\n '所以账号库的目标是「在自己的多个正常账号之间切换更省事」,不是「靠轮换把限流绕过去」。',\n ),\n )\n aboutPane.append(riskCard)\n\n const applyTransportCard = (info: any): void => {\n if (!info) return\n const requested = String(info.requested ?? info.effective ?? 'chromium')\n const effective = String(info.effective ?? requested)\n for (const key of Object.keys(transportBtns)) {\n transportBtns[key].classList.toggle('active', key === requested)\n }\n // 环境不支持时把 Chromium 选项禁掉,别让人选了没反应\n const available = info.chromiumAvailable !== false\n transportBtns.chromium.disabled = !available\n transportBtns.chromium.title = available\n ? ''\n : '本环境拿不到 electron.net.fetch(只有 Electron 的 utility 进程里有)'\n\n const parts = [\n effective === 'chromium' ? '实际生效:Chromium 网络栈' : '实际生效:Node fetch',\n ]\n if (info.degraded) parts.push('配置要求 Chromium,但本环境不支持,已降级为 Node')\n if (effective === 'chromium') parts.push('cipher 列表哈希与 Chrome 一致、ALPN 走 h2')\n transportHint.textContent = parts.join(' · ')\n transportProxyHint.textContent = String(info.hint ?? '')\n }\n\n const saveTransport = async (kind: 'chromium' | 'node'): Promise<void> => {\n transportMsg.textContent = '切换中……'\n try {\n const result = await api('/transport', { method: 'POST', body: JSON.stringify({ transport: kind }) })\n if (result?.ok) {\n applyTransportCard(result)\n transportMsg.textContent =\n `已切换到 ${result.effective === 'chromium' ? 'Chromium 网络栈' : 'Node fetch'},即时生效` +\n (result.persisted === false\n ? '(未能写入配置,重启后会回到上次保存的值)'\n : '(已写入配置,重启后仍生效)')\n } else {\n transportMsg.textContent = `切换失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n transportMsg.textContent = `切换失败:${error?.message ?? error}`\n }\n }\n\n transportTestBtn.addEventListener('click', () => {\n void (async () => {\n transportTestBtn.disabled = true\n transportTestOut.style.display = ''\n transportTestOut.textContent = '正在探测(指纹 / 流式 / 鉴权)……'\n try {\n const result = await api('/diagnostics/net-fetch', {\n method: 'POST',\n body: JSON.stringify({ mode: 'probe' }),\n })\n const lines: string[] = []\n for (const step of result?.results ?? []) {\n const title = String(step.step ?? '')\n if (title.startsWith('①')) {\n lines.push(`① 指纹 ja4=${step.ja4 ?? '-'} HTTP=${step.http_version ?? '-'}`)\n } else if (title.startsWith('②')) {\n lines.push(\n `② 流式 ${step.ok ? '支持' : '不支持'} 分片=${step.chunks ?? 0} abort=${step.abortedEarly ? '生效' : '未生效'}`,\n )\n } else if (title.startsWith('③')) {\n lines.push(`③ 鉴权 HTTP ${step.status ?? '-'} ${step.ok ? '通过' : (step.error ?? '未通过')}`)\n }\n }\n transportTestOut.textContent = lines.length ? lines.join('\\n') : JSON.stringify(result)\n } catch (error: any) {\n transportTestOut.textContent = `探测失败:${error?.message ?? error}`\n } finally {\n transportTestBtn.disabled = false\n }\n })()\n })\n\n void (async () => {\n try {\n applyTransportCard(await api('/transport'))\n } catch {\n transportMsg.textContent = '传输层设置读取失败(宿主未响应)'\n }\n try {\n applyContextCard(await api('/context-mode'))\n } catch {\n contextMsg.textContent = '上下文设置读取失败(宿主未响应)'\n }\n })()\n\n // 账号库首次加载(此时 refresh 已经定义好了)\n void loadAccounts()\n // 台账首次加载\n void loadLedger()\n // 版本 / 数据位置来自 /status,这里主动拉一次让它别等到第一次轮询\n void refresh(true)\n\n /** 渲染宿主返回的节流设置(含可选档位与清理策略)。 */\n const applyGate = (g: any): void => {\n if (!g) return\n concInput.checked = !!g.allowConcurrent\n\n const cap = String(g.maxIntervalMs ?? 30000)\n minRange.max = cap\n maxRange.max = cap\n const lo = Number(g.minRequestIntervalMs ?? 2000)\n const hi = Number(g.maxRequestIntervalMs ?? lo)\n minRange.value = String(lo)\n maxRange.value = String(hi)\n minValue.textContent = `${lo}ms`\n maxValue.textContent = `${hi}ms`\n intervalHint.textContent =\n lo === hi\n ? `固定间隔 ${lo}ms(上下限相等)。建议拉开成区间 —— 固定值方差≈0,是明显的「定时器特征」。`\n : `实际等待在 ${lo}~${hi}ms 之间随机取值(均值约 ${Math.round((lo + hi) / 2)}ms)。`\n\n presetRow.textContent = ''\n presetRow.append(el('span', 'dsw-gate-label', '快捷'))\n const presets: { min: number; max: number }[] =\n g.presets ?? [{ min: 1_500, max: 2_500 }, { min: 2_000, max: 4_000 }, { min: 5_000, max: 9_000 }]\n for (const preset of presets) {\n const isDefault =\n preset.min === (g.defaultMinIntervalMs ?? 2_000) && preset.max === (g.defaultMaxIntervalMs ?? 4_000)\n const btn = el(\n 'button',\n 'dsw-btn ghost dsw-preset',\n `${preset.min}~${preset.max}ms${isDefault ? '(推荐)' : ''}`,\n ) as HTMLButtonElement\n btn.addEventListener('click', () =>\n void saveGate({ minRequestIntervalMs: preset.min, maxRequestIntervalMs: preset.max }),\n )\n if (isDefault && g.minRequestIntervalMs === preset.min && g.maxRequestIntervalMs === preset.max) {\n btn.classList.add('active')\n }\n presetRow.append(btn)\n }\n\n const capBounds = g.maxPromptCharsBounds ?? { min: 120_000, max: 1_500_000 }\n promptRange.min = String(capBounds.min)\n promptRange.max = String(capBounds.max)\n promptRange.step = '20000'\n promptRange.value = String(g.maxPromptChars ?? g.maxPromptCharsDefault ?? capBounds.max)\n paintPromptCap()\n\n const imgBounds = g.maxRefImagesBounds ?? { min: 0, max: 100 }\n imgRange.min = String(imgBounds.min)\n imgRange.max = String(imgBounds.max)\n imgRange.step = '1'\n imgRange.value = String(g.maxRefImages ?? g.maxRefImagesDefault ?? 24)\n paintImageCap()\n\n const mode = g.cleanup?.mode ?? 'deferred'\n for (const key of Object.keys(cleanupBtns)) {\n cleanupBtns[key].classList.toggle('active', key === mode)\n }\n // 三个区间只在「延迟」模式下有意义(立即 = 固定 1.5s/每次一个;不删 = 不清理)\n const showRanges = mode === 'deferred'\n cleanupRanges.style.display = showRanges ? '' : 'none'\n if (showRanges) {\n const batchRange = g.cleanup?.batchRange ?? g.cleanupDefaults?.batch\n if (batchRange) batchPair.set(batchRange.min, batchRange.max)\n const delayRange = g.cleanup?.delayRange ?? g.cleanupDefaults?.delayMs\n if (delayRange) delayPair.set(Math.round(delayRange.min / 1000), Math.round(delayRange.max / 1000))\n const gapRange = g.cleanup?.gapRange ?? g.cleanupDefaults?.gapMs\n if (gapRange) gapPair.set(+(gapRange.min / 1000).toFixed(1), +(gapRange.max / 1000).toFixed(1))\n }\n cleanupHint.textContent =\n mode === 'keep'\n ? '不删:请求最少,但网页端会留下临时会话记录。'\n : mode === 'immediate'\n ? '立即:调用结束后 1.5 秒删掉(老行为,每轮多一个删除请求)。'\n : `延迟:这一轮攒够 ${g.cleanup?.batchSize ?? 8} 个、或最多等 ` +\n `${Math.round((g.cleanup?.delayMs ?? 90_000) / 1000)} 秒就集中清理,并优先用一个请求批量删 ——` +\n '减少「每轮建一个立刻删一个」的机器特征。上面三个区间决定\"这一轮具体攒几个 / 等多久\"。'\n }\n\n const saveGate = async (patch: {\n allowConcurrent?: boolean\n minRequestIntervalMs?: number\n maxRequestIntervalMs?: number\n sessionCleanup?: string\n maxPromptChars?: number\n maxRefImages?: number\n cleanupBatch?: { min: number; max: number }\n cleanupDelayMs?: { min: number; max: number }\n cleanupGapMs?: { min: number; max: number }\n }): Promise<void> => {\n gateMsg.textContent = '保存中……'\n try {\n const result = await api('/gate', { method: 'POST', body: JSON.stringify(patch) })\n if (result?.ok) {\n applyGate(result)\n gateMsg.textContent =\n `已生效:${result.allowConcurrent ? '允许并发(不推荐)' : '串行'} · ` +\n `间隔 ${result.minRequestIntervalMs}~${result.maxRequestIntervalMs}ms · ` +\n `清理 ${result.sessionCleanup ?? result.cleanup?.mode ?? '-'}` +\n (result.cleanup?.batchRange\n ? `(阈值 ${result.cleanup.batchRange.min}~${result.cleanup.batchRange.max} 个 · ` +\n `等待 ${Math.round((result.cleanup.delayRange?.min ?? 0) / 1000)}~` +\n `${Math.round((result.cleanup.delayRange?.max ?? 0) / 1000)}s · ` +\n (result.cleanup.gapRange\n ? `删除间隔 ${(result.cleanup.gapRange.min / 1000).toFixed(1)}~${(result.cleanup.gapRange.max / 1000).toFixed(1)}s`\n : '删除间隔关') +\n ')'\n : '') +\n ` · prompt 上限 ${fmtChars(Number(result.maxPromptChars ?? 0))}` +\n (result.persisted === false ? ` ⚠️ ${result.warning ?? '未能写入配置'}` : '(已写入配置,重启后仍生效)')\n } else {\n gateMsg.textContent = `保存失败:${result?.error ?? '未知原因'}`\n }\n } catch (error: any) {\n gateMsg.textContent = `保存失败:${error?.message ?? error}`\n }\n }\n\n concInput.addEventListener('change', () => void saveGate({ allowConcurrent: concInput.checked }))\n // 拖动中只更新数字,松手(change)才提交,避免一路发请求\n minRange.addEventListener('input', () => {\n minValue.textContent = `${minRange.value}ms`\n })\n maxRange.addEventListener('input', () => {\n maxValue.textContent = `${maxRange.value}ms`\n })\n minRange.addEventListener('change', () => void saveGate({ minRequestIntervalMs: Number(minRange.value) }))\n maxRange.addEventListener('change', () => void saveGate({ maxRequestIntervalMs: Number(maxRange.value) }))\n\n void (async () => {\n try {\n applyGate(await api('/gate'))\n } catch {\n gateMsg.textContent = '节流设置读取失败(宿主未响应)'\n }\n })()\n\n let boostUntil = 0\n\n recoverBtn.addEventListener('click', () => {\n void (async () => {\n recoverBtn.disabled = true\n showMessage('正在从已登录窗口分区读取凭证……(复用上次登录态,不需要重新登录)')\n try {\n const result = await api('/login/recover', { method: 'POST', body: '{}' })\n if (result?.ok) {\n showMessage(`${result.verified ? '✅' : '⚠️'} ${result.message}`, result.verified ? 'ok' : '')\n } else {\n showMessage(`❌ ${result?.message ?? '恢复失败'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`恢复失败:${error?.message ?? error}`, 'err')\n }\n recoverBtn.disabled = false\n await refresh(false)\n })()\n })\n\n externalBtn.addEventListener('click', () => {\n void (async () => {\n externalBtn.disabled = true\n try {\n const result = await api('/login/external', { method: 'POST', body: '{}' })\n if (result?.ok) {\n showMessage(\n `已用系统默认浏览器打开 ${result.url}\\n` +\n '① 在浏览器里正常登录;②按 F12 → Console,粘贴下方「手动粘贴 Token」卡里的那行命令;' +\n '③ 把打印出来的结果粘到那张卡的输入框 → 点「保存并验证」。',\n )\n } else {\n showMessage(`打开失败:${result?.message ?? '未知原因'};请手动在浏览器访问 ${result?.url ?? 'https://chat.deepseek.com'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`打开失败:${error?.message ?? error}`, 'err')\n }\n externalBtn.disabled = false\n })()\n })\n\n browserBtn.addEventListener('click', () => {\n void (async () => {\n browserBtn.disabled = true\n // 轮询(2 秒一次)会无条件把它设回可用 ⇒ 用标记让 applyStatus 跳过重算,\n // 否则用户在等待期间再点一下就会开第二个登录窗口(并发提交凭证)。\n browserBtn.dataset.busy = '1'\n showMessage(\n '正在启动浏览器……会先清掉上次的登录状态,请在其中登录 DeepSeek(登录成功后会自动捕获,不用复制粘贴)。',\n )\n try {\n // fresh:登录前先清掉独立 profile 与登录分区里的登录态。\n // 这个按钮此前是四个登录入口里**唯一没有前置清理**的那个 —— profile 里若还留着\n // 上次那个账号,窗口一打开就是已登录,用户以为在登录、抓回来的却是旧号。\n const result = await api('/login/browser', { method: 'POST', body: JSON.stringify({ fresh: true }) })\n if (result?.mode === 'browser') {\n // 真实浏览器 + CDP:成功即已抓完 token/cookie/指纹头\n if (result.ok) {\n showMessage(`${result.verified ? '✅' : '⚠️'} ${result.message}${result.display ? `(${result.display})` : ''}`, result.verified ? 'ok' : '')\n } else {\n showMessage(`❌ ${result.message ?? `登录未完成(${result.reason ?? '未知'})`}`, 'err')\n }\n } else if (!result?.started) {\n showMessage(\n result?.reason === 'not-electron'\n ? '当前宿主进程无法开 Electron 窗口,且没找到可用的 Edge/Chrome:请用「用我的默认浏览器登录」+ 手动粘贴 Token。'\n : `打开失败:${result?.reason ?? '未知原因'}`,\n 'err',\n )\n } else {\n showMessage('登录窗口已打开,正在旁路捕获登录凭证……')\n }\n } catch (error: any) {\n showMessage(`打开登录窗口失败:${error?.message ?? error}`, 'err')\n }\n delete browserBtn.dataset.busy\n boostUntil = Date.now() + 5 * 60_000\n await refresh(false)\n })()\n })\n\n tokenBtn.addEventListener('click', () => {\n void (async () => {\n const raw = tokenInput.value.trim()\n if (!raw) {\n showMessage('请先粘贴 userToken', 'err')\n return\n }\n const [tokenLine, ...rest] = raw.split('\\n')\n const token = tokenLine.trim()\n const cookie = rest.join(' ').trim()\n tokenBtn.disabled = true\n showMessage('正在校验 token……')\n try {\n const result = await api('/login/token', { method: 'POST', body: JSON.stringify({ token, cookie }) })\n if (result?.ok) {\n if (result.error) {\n showMessage(`⚠️ ${result.error}`, '')\n } else {\n showMessage(`✅ ${result.display ? `账号 ${result.display} · ` : ''}token 校验通过,已保存`, 'ok')\n }\n tokenInput.value = ''\n } else {\n showMessage(`❌ 校验失败:${result?.error ?? '未知原因'}`, 'err')\n }\n } catch (error: any) {\n showMessage(`保存失败:${error?.message ?? error}`, 'err')\n }\n tokenBtn.disabled = false\n await refresh(false)\n })()\n })\n\n /**\n * 二次确认:退出账号是不可逆操作(凭证 + 浏览器登录态都会被清),\n * 所以第一次点击只把按钮变成「确认退出?」,3 秒内再点一次才真的执行。\n * 不用 window.confirm(插件面板里被宿主拦截的风险,且样式不可控)。\n */\n const armConfirm = (button: HTMLButtonElement, label: string, confirmLabel: string, run: () => void): void => {\n let armed = false\n let timer: number | undefined\n const reset = (): void => {\n armed = false\n if (timer !== undefined) window.clearTimeout(timer)\n button.textContent = label\n button.classList.remove('armed')\n }\n button.textContent = label\n button.addEventListener('click', () => {\n if (!armed) {\n armed = true\n button.textContent = confirmLabel\n button.classList.add('armed')\n timer = window.setTimeout(reset, 3_000)\n return\n }\n reset()\n run()\n })\n }\n\n const doLogout = (thenLogin: boolean): void => {\n void (async () => {\n logoutBtn.disabled = true\n switchBtn.disabled = true\n try {\n await api('/logout', { method: 'POST', body: '{}' })\n // 视觉上把 token 输入框也清掉,避免误以为「还是那个账号」\n tokenInput.value = ''\n showMessage(thenLogin ? '已退出当前账号,正在打开登录窗口……' : '已退出当前账号:本地凭证与浏览器登录态都已清除。')\n await refresh(false)\n if (thenLogin) {\n const result = await api('/login/browser', { method: 'POST', body: '{}' })\n if (result?.started === false) {\n showMessage(`已退出账号;打开登录窗口失败:${result?.reason ?? '未知原因'}(可改用手动粘贴 token)`, 'err')\n } else {\n boostUntil = Date.now() + 180_000\n showMessage('已退出账号,登录窗口已打开:请在窗口里登录其它账号,凭证会自动捕获。')\n }\n }\n } catch (error: any) {\n showMessage(`退出失败:${error?.message ?? error}`, 'err')\n } finally {\n await refresh(false)\n }\n })()\n }\n\n armConfirm(logoutBtn, '退出当前账号', '确认退出?', () => doLogout(false))\n armConfirm(switchBtn, '退出并登录其它账号', '确认退出并换号?', () => doLogout(true))\n\n testBtn.addEventListener('click', () => {\n void (async () => {\n testBtn.disabled = true\n testOut.style.display = 'block'\n testOut.className = 'dsw-msg'\n testOut.textContent = '请求中……(首次调用要解 PoW,可能需要几秒)'\n try {\n const result = await api('/test', { method: 'POST', body: JSON.stringify({ model: modelSelect.value }) })\n if (result?.ok) {\n testOut.className = 'dsw-msg ok'\n testOut.textContent = `✅ ${result.ms}ms · ${result.model}\\n${result.text || '(空响应)'}${result.reasoning ? `\\n\\n[思考] ${result.reasoning}` : ''}`\n } else {\n testOut.className = 'dsw-msg err'\n testOut.textContent = `❌ ${result?.code ? `[${result.code}] ` : ''}${result?.error ?? '失败'}`\n }\n } catch (error: any) {\n testOut.className = 'dsw-msg err'\n testOut.textContent = `❌ ${error?.message ?? error}`\n }\n testBtn.disabled = false\n })()\n })\n\n refreshBtn.addEventListener('click', () => void refresh(false))\n\n root.append(page)\n void refresh(false)\n // 倒计时独立走一个 30 秒的定时器:不产生任何请求,只把\"还剩多久\"重新算一遍。\n const countdownTimer = window.setInterval(paintLimit, 30_000)\n\n timer = window.setInterval(() => {\n const active = windowOpen || Date.now() < boostUntil\n // 账号库单独同步:它不在 /status 里,靠这一句才能\"加完账号自己出现\"。\n // 登录流程进行中 3 秒一次(捕获一落地就能看见),空闲 30 秒一次(够让探活补上的\n // 账号名 / 限制状态 / 失败标记自己出现,又不至于一直读盘)。\n if (shouldSyncAccounts(Date.now(), accountsSyncedAt, active)) void syncAccounts()\n if (active) {\n void refresh(true)\n return\n }\n // 空闲态:低频全量刷新(含服务端校验)\n if (Math.random() < 0.15) void refresh(false)\n }, 2000)\n\n return () => {\n disposed = true\n if (timer !== undefined) window.clearInterval(timer)\n window.clearInterval(countdownTimer)\n }\n }, [])\n\n return createElement('div', { ref: hostRef })\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(\n () =>\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register({ name: 'settings.section', id: 'deepseek-web-login', order: 46, label: () => 'DeepSeek 网页登录' }, () =>\n createElement(Panel),\n ),\n ),\n 'deepseek-web-login: settings page',\n )\n}\n"],"mappings":";;;;;;;;EAsCA,MAAM,YAAiB;;;;;;;;EASvB,SAAgB,kBAAkB,MAA+B,IAAS,IAAI,GAAuB;GACnG,IAAI,CAAC,MAAM,OAAO,KAAA;GAClB,IAAI;IACF,MAAM,WAAW,EAAE,2BAA2B,iBAAiB,IAAI;IACnE,OAAO,OAAO,aAAa,YAAY,SAAS,KAAK,IAAI,WAAW,KAAA;GACtE,QAAQ;IAEN;GACF;EACF;;EAGA,SAAgB,kBAAkB,IAAS,IAAI,GAAY;GACzD,OAAO,OAAO,GAAG,uBAAuB;EAC1C;;;;;;;;EASA,SAAgB,oBAAoB,sBAAY,IAAI,KAAK,GAAW;GAClE,MAAM,OAAO,UAAkB,OAAO,KAAK,CAAC,CAAC,SAAS,GAAG,GAAG;GAI5D,OAAO,qBAAqB,GAFvB,IAAI,YAAY,IAAI,IAAI,IAAI,SAAS,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,CAAC,EAAA,GAC9D,IAAI,IAAI,SAAS,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,IAAI,IAAI,IAAI,WAAW,CAAC,IACtC;EACpC;;EAWA,SAAgB,eAAe,OAAyB;GACtD,OAAQ,OAAe,SAAS;EAClC;;;;;;;;;EAUA,eAAsB,eACpB,eACA,SACA,IAAS,IAAI,GACS;GACtB,IAAI,CAAC,kBAAkB,CAAC,GAAG,OAAO,EAAE,MAAM,cAAc;GAIxD,IAAI;GAGJ,IAAI;IACF,MAAM,SAAS,MAAM,EAAE,mBAAoB;KACzC;KACA,OAAO,CAAC;MAAE,aAAa;MAAW,QAAQ,EAAE,oBAAoB,CAAC,OAAO,EAAE;KAAE,CAAC;IAC/E,CAAC;IACD,MAAM,OAAO,MAAM,QAAQ;IAC3B,WAAY,MAAM,OAAO,eAAe;IACxC,MAAM,SAAS,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,mBAAmB,CAAC,CAAC;IACnE,MAAM,SAAS,MAAM;IACrB,WAAW,KAAA;IACX,OAAO;KAAE,MAAM;KAAS,MAAM,OAAO,QAAQ,SAAS,YAAY,OAAO,OAAO,OAAO,OAAO;IAAc;GAC9G,SAAS,OAAY;IACnB,IAAI,UACF,IAAI;KACF,MAAM,SAAS,MAAM,KAAK;IAC5B,QAAQ,CAAC;IAEX,IAAI,eAAe,KAAK,GAAG,OAAO,EAAE,MAAM,YAAY;IACtD,OAAO;KAAE,MAAM;KAAU,QAAQ,GAAG,OAAO,QAAQ,GAAG,GAAG,OAAO,WAAW,QAAQ,KAAK;IAAE;GAC5F;EACF;;;;;;;;EASA,SAAgB,aAAa,MAAgB,UAAqC;GAChF,OAAO,IAAI,SAAS,YAAY;IAC9B,MAAM,QAAQ,IAAI,cAAc,OAAO;IACvC,MAAM,OAAO;IACb,MAAM,SAAS;IACf,MAAM,MAAM,UAAU;IACtB,IAAI,UAAU;IAGd,MAAM,QAAQ,iBAAiB,OAAO,KAAA,CAAS,GAAG,GAAW;IAC7D,MAAM,UAAU,SAA2B;KACzC,IAAI,SAAS;KACb,UAAU;KACV,aAAa,KAAK;KAClB,MAAM,OAAO;KACb,QAAQ,IAAI;IACd;IACA,MAAM,iBAAiB,gBAAgB,OAAO,MAAM,QAAQ,EAAE,CAAC;IAE/D,MAAM,iBAAiB,gBAAgB,OAAO,KAAA,CAAS,CAAC;IACxD,IAAI,KAAK,YAAY,KAAK;IAC1B,MAAM,MAAM;GACd,CAAC;EACH;;;EAIA,MAAa,0BAA0B;;;;;;;EAevC,eAAsB,iBAAiB,MAAY,IAAS,IAAI,GAA0B;GACxF,MAAM,OAAO,KAAK,QAAQ;GAE1B,IAAI,OAAO,KAAK,SAAS,YAAY,KAAK,OAAA,SACxC,OAAO;IACL,MAAM;IACN;IACA,QAAQ,MAAM,KAAK,KAAK,KAAK,OAAO,IAAI,EAAE,YAAY,KAAK,MAAM,0BAA0B,OAAO,IAAI,EAAE;GAC1G;GAEF,MAAM,OAAO,kBAAkB,MAAM,CAAC;GACtC,IAAI,MAAM,OAAO;IAAE,MAAM;IAAQ;IAAM;GAAK;GAC5C,IAAI;IACF,OAAO;KAAE,MAAM;KAAW,SAAS,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC;KAAG;IAAK;GACzE,SAAS,OAAY;IACnB,OAAO;KAAE,MAAM;KAAc;KAAM,QAAQ,GAAG,OAAO,WAAW;IAAQ;GAC1E;EACF;;;;;;;;ECtFA,SAAgB,oBACd,OACA,MAAc,KAAK,IAAI,GACQ;GAC/B,MAAM,OAAO,SAAS,CAAC;GACvB,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;GAC9B,MAAM,aAAa,KAAK,QAAQ,SAAS,CAAC,KAAK,WAAW,OAAO,SAAS,KAAK,SAAS,CAAC;GACzF,IAAI;GACJ,KAAK,MAAM,QAAQ,YAAY;IAC7B,MAAM,YAAY,KAAK;IACvB,IAAI,CAAC,UAAU,YAAY,OAAO,WAChC,SAAS;KAAE,MAAM,KAAK;KAAM;KAAW,WAAW,YAAY,OAAO;IAAW;GAEpF;GACA,OAAO;IACL,OAAO,KAAK;IACZ,cAAc,KAAK,SAAS,WAAW;IACvC,iBAAiB,WAAW;IAC5B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC7B;EACF;;EAGA,SAAgB,kBAAkB,UAA0B;GAC1D,IAAI,YAAY,GAAG,OAAO;GAC1B,IAAI,YAAY,GAAG,OAAO,MAAM,KAAK,MAAM,QAAQ,EAAE;GAErD,OAAO,MADO,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,EAAE,CACjC,EAAE;EACrB;;;;;;;;;EAUA,SAAgB,mBACd,SACA,MAAc,KAAK,IAAI,GACf;GACR,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,QAAQ,CAAC,GAAG,QAAQ,MAAM,GAAG;GACnC,MAAM,KAAK,QAAQ,eAAe,IAAI,GAAG,QAAQ,aAAa,QAAQ,MAAM;GAC5E,IAAI,QAAQ,kBAAkB,GAAG,MAAM,KAAK,GAAG,QAAQ,gBAAgB,KAAK;GAC5E,IAAI,QAAQ,QAAQ;IAGlB,MAAM,YAAY,QAAQ,OAAO,YAAY,OAAO;IACpD,MAAM,KAAK,GAAG,QAAQ,OAAO,KAAK,GAAG,kBAAkB,QAAQ,GAAG;GACpE;GACA,OAAO,MAAM,KAAK,KAAK;EACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECtIA,MAAa,0BAA0B;;EAGvC,MAAa,wBAAwB;;;;;;;;EASrC,SAAgB,mBAAmB,KAAa,cAAsB,QAA0B;GAC9F,MAAM,MAAM,SAAS,0BAA0B;GAC/C,OAAO,MAAM,gBAAgB;EAC/B;;;;;;;;;;;;;;;;EAiBA,SAAgB,kBAAkB,SAA0B;GAC1D,MAAM,OAAQ,WAAW,CAAC;GAC1B,MAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;GACjE,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;GACjE,IAAI;IACF,OAAO,KAAK,UAAU;KAAE,UAAU,KAAK,YAAY;KAAM;KAAU;KAAQ;IAAS,CAAC;GACvF,QAAQ;IAEN,OAAO;GACT;EACF;;;;;;;;;;ECrDA,MAAa,SAAS,CAAC,OAAO;EAE9B,MAAM,MAAM;EAkDZ,eAAe,IAAI,MAAc,MAAkC;GAKjE,OAAO,OAAM,MAJU,MAAM,MAAM,MAAM;IACvC,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,GAAG;GACL,CAAC,EAAA,CACqB,KAAK;EAC7B;;;;;;;;;;;;;;;EAgBA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0Nf,SAAS,UAAU,OAAwC;GACzD,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO;GAClE,MAAM,OAAO,IAAI,KAAK,KAAK;GAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;GACzC,MAAM,sBAAM,IAAI,KAAK;GACrB,MAAM,UAAU,KAAK,aAAa,MAAM,IAAI,aAAa;GACzD,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;GACrG,OAAO,UAAU,OAAO,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,KAAK,QAAQ,EAAE,GAAG;EACtE;;EAGA,SAAS,QAAQ,OAA+B;GAC9C,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,QAAQ;GACnC,IAAI,CAAC,OAAO,SAAS,EAAE,GAAG,OAAO;GACjC,MAAM,OAAO,KAAK,IAAI,IAAI;GAC1B,IAAI,OAAO,KAAQ,OAAO;GAC1B,IAAI,OAAO,MAAW,OAAO,GAAG,KAAK,MAAM,OAAO,GAAM,EAAE;GAC1D,IAAI,OAAO,OAAY,OAAO,GAAG,KAAK,MAAM,OAAO,IAAS,EAAE;GAC9D,OAAO,GAAG,KAAK,MAAM,OAAO,KAAU,EAAE;EAC1C;;EAGA,SAAS,UAAU,SAAiC;GAClD,IAAI,CAAC,WAAW,CAAC,OAAO,SAAS,OAAO,GAAG,OAAO;GAClD,MAAM,OAAO,UAAU,KAAK,IAAI;GAChC,IAAI,QAAQ,GAAG,OAAO;GACtB,MAAM,eAAe,KAAK,KAAK,OAAO,GAAM;GAC5C,MAAM,QAAQ,KAAK,MAAM,eAAe,EAAE;GAC1C,MAAM,UAAU,eAAe;GAC/B,IAAI,QAAQ,GAAG,OAAO,MAAM,MAAM,MAAM,QAAQ;GAChD,OAAO,MAAM,QAAQ;EACvB;EAEA,SAAS,GAAG,KAAa,KAAc,MAA4B;GACjE,MAAM,OAAO,SAAS,cAAc,GAAG;GACvC,IAAI,KAAK,KAAK,YAAY;GAC1B,IAAI,SAAS,KAAA,GAAW,KAAK,cAAc;GAC3C,OAAO;EACT;;EAGA,SAAS,QAAa;GACpB,MAAM,WAAA,GAAUA,MAAAA,OAAAA,CAA8B,IAAI;GAElD,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,OAAO,QAAQ;IACrB,IAAI,CAAC,MAAM;IACX,IAAI,WAAW;IACf,IAAI;IAEJ,MAAM,QAAQ,SAAS,cAAc,OAAO;IAC5C,MAAM,cAAc;IAEpB,MAAM,OAAO,GAAG,OAAO,UAAU;IACjC,MAAM,QAAQ,GAAG,MAAM,aAAa,qBAAqB;IAOzD,MAAM,MAAM,GAAG,KAAK,SAAS;IAC7B,IAAI,OAAO,sDAAsD;IACjE,IAAI,OAAO,GAAG,QAAQ,eAAe,cAAc,CAAC;IACpD,IAAI,OAAO,GAAG;IAEd,KAAK,OAAO,OAAO,OAAO,GAAG;IAI7B,MAAM,UAAU,GAAG,OAAO,WAAW;IACrC,QAAQ,MAAM,UAAU;IACxB,KAAK,OAAO,OAAO;IAEnB,MAAM,eAAe,MAAc,OAA0B,OAAa;KACxE,QAAQ,cAAc;KACtB,QAAQ,YAAY,YAAY,OAAO,IAAI,SAAS;KACpD,QAAQ,MAAM,UAAU;IAC1B;IAKA,MAAM,WAAW;KAAC;KAAW;KAAS;KAAQ;KAAa;KAAW;IAAO;IAC7E,MAAM,aAAqC;KACzC,SAAS;KACT,OAAO;KACP,MAAM;KACN,WAAW;KACX,SAAS;KACT,OAAO;IACT;IACA,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,MAAM,gBAAgB,GAAG,OAAO,UAAU;IAC1C,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,QAAqC;KACzC,SAAS;KACT,OAAO;KACP,MAAM;KACN,WAAW;KACX,SAAS;KACT,OAAO;IACT;IACA,MAAM,aAAgD,CAAC;IACvD,MAAM,aAAa,QAAsB;KACvC,KAAK,MAAM,QAAQ,UAAU;MAC3B,MAAM,KAAK,SAAS;MACpB,MAAM,KAAK,CAAC,SAAS,CAAC;MACtB,WAAW,KAAK,CAAC,UAAU,OAAO,UAAU,EAAE;MAC9C,WAAW,KAAK,CAAC,aAAa,iBAAiB,OAAO,EAAE,CAAC;KAC3D;IACF;IACA,MAAM,SAAS,GAAG,OAAO,UAAU;IACnC,OAAO,aAAa,QAAQ,SAAS;IACrC,KAAK,MAAM,OAAO,UAAU;KAC1B,MAAM,MAAM,GAAG,UAAU,WAAW,WAAW,IAAI;KACnD,IAAI,OAAO;KACX,IAAI,aAAa,QAAQ,KAAK;KAC9B,IAAI,iBAAiB,eAAe,UAAU,GAAG,CAAC;KAClD,WAAW,OAAO;KAClB,OAAO,OAAO,GAAG;KACjB,MAAM,IAAI,CAAC,aAAa,QAAQ,UAAU;IAC5C;IACA,KAAK,OAAO,QAAQ,GAAG,SAAS,KAAK,QAAQ,MAAM,IAAI,CAAC;IACxD,UAAU,SAAS;IAOnB,MAAM,YAAY,CAAC,UAAU,SAAS;IACtC,MAAM,cAAsC;KAAE,QAAQ;KAAQ,SAAS;IAAM;IAC7E,MAAM,iBAAiB,GAAG,OAAO,UAAU;IAC3C,MAAM,kBAAkB,GAAG,OAAO,UAAU;IAC5C,MAAM,YAAyC;KAAE,QAAQ;KAAgB,SAAS;IAAgB;IAClG,MAAM,cAAiD,CAAC;IACxD,MAAM,iBAAiB,QAAsB;KAC3C,KAAK,MAAM,QAAQ,WAAW;MAC5B,MAAM,KAAK,SAAS;MACpB,UAAU,KAAK,CAAC,SAAS,CAAC;MAC1B,YAAY,KAAK,CAAC,UAAU,OAAO,UAAU,EAAE;MAC/C,YAAY,KAAK,CAAC,aAAa,iBAAiB,OAAO,EAAE,CAAC;KAC5D;IACF;IACA,MAAM,aAAa,GAAG,OAAO,aAAa;IAC1C,WAAW,aAAa,QAAQ,SAAS;IACzC,KAAK,MAAM,OAAO,WAAW;KAC3B,MAAM,MAAM,GAAG,UAAU,cAAc,YAAY,IAAI;KACvD,IAAI,OAAO;KACX,IAAI,aAAa,QAAQ,KAAK;KAC9B,IAAI,iBAAiB,eAAe,cAAc,GAAG,CAAC;KACtD,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;KACrB,UAAU,IAAI,CAAC,aAAa,QAAQ,UAAU;IAChD;IACA,YAAY,OAAO,YAAY,gBAAgB,eAAe;IAG9D,cAAc,QAAQ;IAGtB,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,MAAM,aAAa,GAAG,KAAK;IAC3B,WAAW,OAAO,GAAG,QAAQ,QAAQ,MAAM,CAAC;IAC5C,MAAM,QAAQ,GAAG,QAAQ,iBAAiB,OAAO;IACjD,WAAW,OAAO,KAAK;IACvB,UAAU,OAAO,UAAU;IAC3B,UAAU,OAAO,SAAS;IAE1B,MAAM,WAAW,GAAG,OAAO,QAAQ;IACnC,UAAU,OAAO,QAAQ;IAMzB,IAAI,qBAAoC;IACxC,MAAM,WAAW,GAAG,KAAK,oBAAoB;IAC7C,SAAS,MAAM,UAAU;IACzB,SAAS,MAAM,YAAY;IAC3B,UAAU,OAAO,QAAQ;IACzB,MAAM,mBAAyB;KAC7B,IAAI,CAAC,oBAAoB;MACvB,SAAS,MAAM,UAAU;MACzB;KACF;KACA,MAAM,OAAO,UAAU,kBAAkB;KACzC,SAAS,MAAM,UAAU;KACzB,SAAS,cACP,SAAS,QACL,YAAY,UAAU,kBAAkB,EAAE,MAC1C,YAAY,KAAK,GAAG,UAAU,kBAAkB,EAAE;IAC1D;IAKA,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,YAAY,OAAO,GAAG,OAAO,gBAAgB,MAAM,CAAC;IACpD,MAAM,cAAc,GAAG,OAAO,QAAQ;IACtC,YAAY,OAAO,WAAW;IAC9B,MAAM,iBAAiB,GAAG,OAAO,SAAS;IAC1C,eAAe,MAAM,YAAY;IACjC,MAAM,YAAY,GAAG,UAAU,kBAAkB,QAAQ;IACzD,MAAM,YAAY,GAAG,UAAU,iBAAiB,WAAW;IAC3D,eAAe,OAAO,WAAW,SAAS;IAC1C,YAAY,OAAO,cAAc;IACjC,MAAM,cAAc,GAClB,KACA,YACA,qFAEF;IACA,YAAY,OAAO,WAAW;IAE9B,MAAM,eAAe,GAAG,OAAO,SAAS;IACxC,aAAa,MAAM,YAAY;IAC/B,MAAM,aAAa,GAAG,UAAU,WAAW,SAAS;IACpD,MAAM,cAAc,GAAG,UAAU,iBAAiB,YAAY;IAC9D,MAAM,aAAa,GAAG,UAAU,iBAAiB,UAAU;IAC3D,MAAM,aAAa,GAAG,UAAU,iBAAiB,MAAM;IACvD,aAAa,OAAO,YAAY,aAAa,YAAY,UAAU;IACnE,UAAU,OAAO,YAAY;IAC7B,UAAU,OACR,GACE,KACA,YACA,iHAEF,CACF;IACA,UAAU,OACR,GACE,KACA,YACA,iDACF,CACF;IACA,eAAe,OAAO,WAAW,WAAW;;;;;;;;;IAiB5C,MAAM,sBAAkE;KACtE,MAAM,OAAO,GAAG,KAAK,yBAAyB,EAAE;KAChD,IAAI,OAAO;KACX,OAAO;MACL;MACA,IAAI,cAAsB;OACxB,OAAO;MACT;MACA,IAAI,YAAY,OAAe;OAC7B,OAAO,SAAS;OAChB,KAAK,cAAc;OACnB,KAAK,YAAY,iBAAiB,KAAK,IAAI,IACvC,gBACA,2BAA2B,KAAK,IAAI,IAClC,eACA;MACR;KACF;IACF;IAEA,MAAM,eAAe,GAAG,OAAO,UAAU;IACzC,MAAM,eAAe,GAAG,OAAO,cAAc;IAC7C,aAAa,OAAO,GAAG,QAAQ,QAAQ,KAAK,CAAC;IAC7C,MAAM,gBAAgB,GAAG,QAAQ,iBAAiB,GAAG;IACrD,aAAa,OAAO,aAAa;IACjC,aAAa,OAAO,YAAY;IAChC,aAAa,OACX,GACE,KACA,YACA,kDACF,CACF;IAEA,MAAM,eAAe,GAAG,MAAM,cAAc;IAC5C,aAAa,OAAO,YAAY;IAChC,MAAM,gBAAgB,GAAG,KAAK,YAAY,sCAAsC;IAChF,aAAa,OAAO,aAAa;IAEjC,MAAM,kBAAkB,GAAG,OAAO,SAAS;IAC3C,gBAAgB,MAAM,YAAY;IAGlC,MAAM,gBAAgB,GAAG,UAAU,WAAW,WAAW;IACzD,MAAM,YAAY,GAAG,UAAU,iBAAiB,OAAO;IACvD,MAAM,YAAY,GAAG,UAAU,iBAAiB,OAAO;IAOvD,MAAM,qBAAqB,GAAG,UAAU,iBAAiB,MAAM;IAC/D,mBAAmB,QAAQ;IAC3B,MAAM,cAAc,GAAG,UAAU,iBAAiB,MAAM;IACxD,YAAY,QAAQ;IACpB,gBAAgB,OAAO,eAAe,WAAW,WAAW,oBAAoB,WAAW;IAC3F,aAAa,OAAO,eAAe;IAKnC,aAAa,OACX,GACE,KACA,YACA,oJAGF,CACF;IACA,MAAM,cAAc,cAAc;IAClC,aAAa,OAAO,YAAY,IAAI;IACpC,aAAa,OACX,GACE,KACA,YACA,6CACF,CACF;IACA,gBAAgB,OAAO,YAAY;;;;;;;IAQnC,MAAM,cAAc,SAAc,WAA+B;KAC/D,MAAM,MAAM,OAAO,SAAS,OAAO,EAAE;KACrC,MAAM,YAAY,gBAAgB,IAAI,GAAG;KACzC,MAAM,OAAO,GAAG,OAAO,eAAe;KACtC,MAAM,SAAS,GAAG,QAAQ,WAAW,YAAY,MAAM,GAAG;KAC1D,OAAO,QAAQ,YAAY,OAAO;KAClC,OAAO,iBAAiB,eAAe;MACrC,IAAI,gBAAgB,IAAI,GAAG,GAAG,gBAAgB,OAAO,GAAG;WACnD,gBAAgB,IAAI,GAAG;MAC5B,uBAAuB;MAEvB,eAAe;KACjB,CAAC;KACD,KAAK,OAAO,QAAQ,GAAG,QAAQ,SAAS,OAAO,SAAS,QAAQ,EAAE,CAAC,CAAC;KACpE,KAAK,OAAO,GAAG,QAAQ,UAAU,GAAG,MAAM,QAAQ,SAAS,QAAQ,IAAI,QAAQ,SAAS,SAAS,EAAE,GAAG,CAAC;KACvG,KAAK,OAAO,GAAG,QAAQ,SAAS,CAAC;KACjC,IAAI,SAAS,SAAS;MACpB,MAAM,iBAAiB,GAAG,UAAU,iBAAiB,IAAI;MACzD,eAAe,iBAAiB,eAAe;OAC7C,MAAM,QAAQ,GAAG,SAAS,gBAAgB;OAC1C,MAAM,QAAQ,OAAO,QAAQ,QAAQ,EAAE;OACvC,MAAM,cAAc;OACpB,KAAK,OAAO,KAAK;OACjB,MAAM,MAAM;OACZ,IAAI,UAAU;OACd,MAAM,eAAqB;QACzB,IAAI,SAAS;QACb,UAAU;QACV,mBAAwB,OAAO,QAAQ,OAAO,GAAG,MAAM,KAAK;OAC9D;OACA,MAAM,iBAAiB,YAAY,UAAU;QAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;QAClC,IAAI,MAAM,QAAQ,UAAU;SAC1B,UAAU;SACV,eAAe;QACjB;OACF,CAAC;OACD,MAAM,iBAAiB,QAAQ,MAAM;MACvC,CAAC;MACD,MAAM,iBAAiB,GAAG,UAAU,kBAAkB,IAAI;MAC1D,eAAe,QAAQ;MACvB,IAAI,QAAQ;MACZ,eAAe,iBAAiB,eAAe;OAC7C,IAAI,CAAC,OAAO;QACV,QAAQ;QACR,eAAe,cAAc;QAC7B,OAAO,iBAAiB;SACtB,QAAQ;SACR,eAAe,cAAc;QAC/B,GAAG,GAAI;QACP;OACF;OACA,mBAAwB,OAAO,QAAQ,OAAO,CAAC;MACjD,CAAC;MACD,KAAK,OAAO,gBAAgB,cAAc;KAC5C;KACA,OAAO;IACT;IAEA,MAAM,kBAAkB,SAAoB;KAC1C,MAAM,QAAe,MAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,WAAW,CAAC;KACtE,MAAM,SAAgB,MAAM,QAAQ,MAAM,MAAM,IAAI,KAAK,SAAS,CAAC;KACnE,MAAM,WAAkB,MAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,WAAW,CAAC;KACzE,cAAc,cAAc,GAAG,MAAM,OAAO;KAC5C,cAAc,YAAY,aAAa,MAAM,SAAS,OAAO;KAC7D,cAAc,SAAS,MAAM,SAAS;KACtC,aAAa,cAAc;KAG3B,IAAI,CAAC,SAAS,QAAQ;MACpB,KAAK,MAAM,QAAQ,OAAO,aAAa,OAAO,WAAW,MAAM,QAAQ,KAAK,CAAC;MAC7E;KACF;KACA,KAAK,MAAM,WAAW,UAAU;MAC9B,aAAa,OAAO,WAAW,SAAS,MAAM,CAAC;MAC/C,IAAI,gBAAgB,IAAI,OAAO,SAAS,OAAO,EAAE,CAAC,GAAG;MACrD,KAAK,MAAM,QAAQ,MAAM,QAAQ,SAAS,QAAQ,IAAI,QAAQ,WAAW,CAAC,GACxE,aAAa,OAAO,WAAW,MAAM,QAAQ,IAAI,CAAC;KAEtD;IACF;;;;;;;;IASA,MAAM,kBAAkB,IAAY,UAAwB;KAC1D,CAAM,YAAY;MAGhB,YAAY,cAAc,OAAO,MAAM;MACvC,IAAI;OACF,MAAM,OAAO,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;OAAE,CAAC;OACzF,IAAI,MAAM,OAAO,OAAO;QACtB,YAAY,cAAc,QAAQ,MAAM,SAAS;QACjD;OACF;OACA,aAAa,KAAK,IAAI,IAAI;OAC1B,YAAY,cAAc,OACxB,MAAM,QACJ,8CACJ;OACA,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,YAAY,OAAO;QAC7B,YAAY,cAAc,YAAY,QAAQ,UAAU,OAAO;QAC/D;OACF;OACA,YAAY,cAAc,QAAQ,UAC9B,IAAI,MAAM,uCACV,QAAQ,QACN,IAAI,MAAM,2BACV;OACN,MAAM,aAAa;MACrB,SAAS,OAAY;OACnB,YAAY,cAAc,UAAU,OAAO,WAAW;MACxD,UAAU;OACR,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;MAC5C;KACF,EAAA,CAAG;IACL;IAEA,MAAM,cAAc,MAAW,SAAgB,CAAC,GAAG,SAAS,UAAuB;KACjF,MAAM,MAAM,GAAG,MAAM,cAAc,KAAK,WAAW,YAAY,KAAK,SAAS,aAAa,IAAI;KAC9F,MAAM,OAAO,GAAG,OAAO,kBAAkB;KACzC,MAAM,QAAQ,GAAG,OAAO,mBAAmB;KAC3C,MAAM,OAAO,GAAG,QAAQ,KAAA,GAAW,KAAK,SAAS,KAAK,EAAE,CAAC;KACzD,IAAI,KAAK,UAAU,MAAM,OAAO,GAAG,QAAQ,gBAAgB,MAAM,CAAC;KAClE,IAAI,KAAK,YAAY,MAAM,OAAO,GAAG,QAAQ,iBAAiB,OAAO,CAAC;KAEtE,IADgB,KAAK,SAAS,OAAO,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,UAAU,KAAK,IAAI,GAEjG,MAAM,OAAO,GAAG,QAAQ,iBAAiB,SAAS,UAAU,KAAK,MAAM,OAAO,GAAG,CAAC;KAIpF,IAAI,KAAK,iBAAiB,MAAM,OAAO,GAAG,QAAQ,iBAAiB,UAAU,CAAC;KAC9E,KAAK,OAAO,KAAK;KAEjB,MAAM,OAAiB,CAAC;KACxB,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK,OAAO;KACxC,IAAI,KAAK,YAAY,KAAK,KAAK,GAAG,UAAU,KAAK,UAAU,EAAE,IAAI;KACjE,KAAK,KAAK,KAAK,iBAAiB,QAAQ,QAAQ,KAAK,cAAc,MAAM,MAAM;KAC/E,MAAM,aAAoB,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC;KAC9E,KAAK,KAAK,UAAU,mBAAmB,oBAAoB,UAAU,CAAC,GAAG;KACzE,KAAK,OAAO,GAAG,OAAO,oBAAoB,KAAK,KAAK,KAAK,CAAC,CAAC;KAM3D,IAAI,KAAK,iBAGP,KAAK,OACH,GACE,OACA,mBACA,MAAM,QAAQ,KAAK,gBAAgB,EAAE,EAAE,OAAO,KAAK,gBAAgB,SACrE,CACF;KAEF,IAAI,OAAO,IAAI;KAEf,MAAM,UAAU,GAAG,OAAO,qBAAqB;KAG/C,IAAI,KAAK,iBAAiB;MACxB,MAAM,aAAa,GAAG,UAAU,sBAAsB,IAAI;MAC1D,WAAW,QAAQ;MACnB,WAAW,iBAAiB,eAAe,eAAe,KAAK,IAAI,KAAK,SAAS,KAAK,EAAE,CAAC;MACzF,QAAQ,OAAO,UAAU;KAC3B;KACA,IAAI,KAAK,UACP,QAAQ,OAAO,GAAG,QAAQ,YAAY,KAAK,CAAC;UACvC;MACL,MAAM,SAAS,GAAG,UAAU,4BAA4B,IAAI;MAC5D,OAAO,iBAAiB,eAAe,KAAK,gBAAgB,KAAK,EAAE,CAAC;MACpE,QAAQ,OAAO,MAAM;KACvB;KAIA,MAAM,YAAY,GAAG,UAAU,4BAA4B,IAAI;KAC/D,IAAI,UAAU;KACd,UAAU,iBAAiB,eAAe;MACxC,IAAI,SAAS;MACb,UAAU;MACV,MAAM,QAAQ,GAAG,SAAS,gBAAgB;MAC1C,MAAM,QAAQ,KAAK,SAAS;MAC5B,MAAM,cAAc;MACpB,KAAK,OAAO,KAAK;MACjB,MAAM,MAAM;MACZ,IAAI,UAAU;MACd,MAAM,eAAqB;OACzB,IAAI,SAAS;OACb,UAAU;OACV,iBAAsB,KAAK,IAAI,MAAM,KAAK;MAC5C;MACA,MAAM,iBAAiB,YAAY,UAAU;OAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;OAClC,IAAI,MAAM,QAAQ,UAAU;QAC1B,UAAU;QACV,aAAkB;OACpB;MACF,CAAC;MACD,MAAM,iBAAiB,QAAQ,MAAM;KACvC,CAAC;KACD,QAAQ,OAAO,SAAS;KAIxB,MAAM,WAAW,GAAG,UAAU,cAAc;KAC5C,SAAS,QAAQ;KACjB,SAAS,OAAO,IAAI,OAAO,OAAO,EAAE,CAAC;KACrC,KAAK,MAAM,SAAS,QAAQ,SAAS,OAAO,IAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,GAAG,OAAO,OAAO,MAAM,EAAE,CAAC,CAAC;KAC1G,SAAS,QAAQ,OAAO,KAAK,WAAW,EAAE;KAC1C,SAAS,iBAAiB,gBAAgB,KAAK,mBAAmB,KAAK,IAAI,SAAS,KAAK,CAAC;KAC1F,QAAQ,OAAO,QAAQ;KAGvB,MAAM,YAAY,GAAG,UAAU,6BAA6B,IAAI;KAChE,IAAI,QAAQ;KACZ,UAAU,iBAAiB,eAAe;MACxC,IAAI,CAAC,OAAO;OACV,QAAQ;OACR,UAAU,cAAc;OACxB,UAAU,UAAU,IAAI,OAAO;OAC/B,OAAO,iBAAiB;QACtB,QAAQ;QACR,UAAU,cAAc;QACxB,UAAU,UAAU,OAAO,OAAO;OACpC,GAAG,GAAK;OACR;MACF;MACA,kBAAuB,KAAK,EAAE;KAChC,CAAC;KACD,QAAQ,OAAO,SAAS;KACxB,IAAI,OAAO,OAAO;KAClB,OAAO;IACT;;;;;;;;IASA,MAAM,iBAAiB,SAAoB;KACzC,sBAAsB;KACtB,MAAM,YAAY,kBAAkB,IAAI;KACxC,IAAI,aAAa,cAAc,wBAAwB;KACvD,yBAAyB;KACzB,eAAe,IAAI;IACrB;;IAGA,MAAM,uBAA6B;KACjC,IAAI,qBAAqB,eAAe,mBAAmB;IAC7D;;IAGA,MAAM,eAAe,YAA2B;KAC9C,IAAI;MACF,cAAc,MAAM,IAAI,WAAW,CAAC;KACtC,SAAS,OAAY;MACnB,YAAY,cAAc,WAAW,OAAO,WAAW;KACzD,UAAU;MACR,mBAAmB,KAAK,IAAI;KAC9B;IACF;;;;;;;;;;;IAYA,MAAM,eAAe,YAA2B;KAC9C,mBAAmB,KAAK,IAAI;KAC5B,IAAI;MACF,cAAc,MAAM,IAAI,WAAW,CAAC;KACtC,QAAQ,CAER;IACF;;;;;;;;IASA,MAAM,WAAW,OAAO,MAAc,MAAe,WAAkC;KACrF,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,MAAM;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,IAAI;MAAE,CAAC;MAC7E,IAAI,QAAQ,OAAO,OAAO;OACxB,YAAY,cAAc,UAAU,QAAQ,SAAS;OACrD;MACF;MACA,YAAY,cAAc;KAC5B,SAAS,OAAY;MACnB,YAAY,cAAc,UAAU,OAAO,WAAW;KACxD,UAAU;MACR,MAAM,aAAa;KACrB;IACF;IAEA,MAAM,sBAAsB,IAAY,YACtC,SACE,0BACA;KAAE;KAAI;IAAQ,GACd,UAAU,SAAS,iBACrB;IAEF,MAAM,sBAAsB,IAAY,SACtC,SAAS,0BAA0B;KAAE;KAAI;IAAK,GAAG,UAAU,KAAK,KAAK,EAAE,GAAG;IAE5E,MAAM,sBAAsB,OAC1B,SAAS,0BAA0B,EAAE,GAAG,GAAG,6BAA6B;;IAG1E,IAAI,gBAAyC;IAC7C,MAAM,uBAA6B;KACjC,IAAI,iBAAiB,cAAc,aAAa;MAC9C,cAAc,MAAM;MACpB;KACF;KACA,MAAM,QAAQ,GAAG,SAAS,gBAAgB;KAC1C,MAAM,cAAc;KACpB,gBAAgB,OAAO,KAAK;KAC5B,MAAM,MAAM;KACZ,gBAAgB;KAChB,IAAI,UAAU;KACd,MAAM,gBAAsB;MAC1B,UAAU;MACV,gBAAgB;MAChB,MAAM,OAAO;KACf;KACA,MAAM,eAAqB;MACzB,IAAI,SAAS;MACb,MAAM,OAAO,MAAM;MACnB,QAAQ;MACR,IAAI,CAAC,KAAK,KAAK,GAAG;MAClB,SAAc,0BAA0B,EAAE,KAAK,GAAG,SAAS,KAAK,KAAK,EAAE,GAAG;KAC5E;KACA,MAAM,iBAAiB,YAAY,UAAU;MAC3C,IAAI,MAAM,QAAQ,SAAS,OAAO;MAClC,IAAI,MAAM,QAAQ,UAAU,QAAQ;KACtC,CAAC;KACD,MAAM,iBAAiB,QAAQ,MAAM;IACvC;;;;;;;IAQA,MAAM,uBAAuB,YAA2B;KACtD,mBAAmB,WAAW;KAC9B,MAAM,WAAW,mBAAmB;KACpC,mBAAmB,cAAc;KACjC,YAAY,cAAc;KAC1B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,qBAAqB;OAAE,QAAQ;OAAQ,MAAM;MAAK,CAAC;MAC5E,IAAI,QAAQ,OAAO,OAAO;OACxB,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,MAAM,SAAS,OAAO,QAAQ,UAAU,CAAC;MACzC,YAAY,cACV,SAAS,IACL,OAAO,QAAQ,WAAW,EAAE,OAAO,QAAQ,UAAU,EAAE,OAAO,OAAO,mBACrE,OAAO,QAAQ,WAAW,EAAE;KACpC,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD,UAAU;MACR,mBAAmB,WAAW;MAC9B,mBAAmB,cAAc;MAEjC,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;KAC5C;IACF;IAEA,mBAAmB,iBAAiB,eAAe,KAAK,qBAAqB,CAAC;IAC9E,YAAY,iBAAiB,eAAe,eAAe,CAAC;IAE5D,MAAM,kBAAkB,OAAO,OAA8B;KAC3D,YAAY,cAAc;KAC1B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;MAAE,CAAC;MAC7F,IAAI,CAAC,QAAQ,IAAI;OACf,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,YAAY,cAAc;MAC1B,MAAM,aAAa;MACnB,MAAM,QAAQ,KAAK;MACnB,YAAY,cAAc;KAC5B,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD;IACF;IAEA,MAAM,oBAAoB,OAAO,OAA8B;KAC7D,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,CAAC;MAAE,CAAC;MAC7F,IAAI,CAAC,QAAQ,IAAI;OACf,YAAY,cAAc,QAAQ,QAAQ,SAAS;OACnD;MACF;MACA,YAAY,cAAc;MAC1B,MAAM,aAAa;MACnB,MAAM,QAAQ,IAAI;KACpB,SAAS,OAAY;MACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;KACtD;IACF;IAEA,MAAM,mBAAmB,OAAO,IAAY,UAAiC;KAC3E,IAAI;MACF,MAAM,IAAI,oBAAoB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU;QAAE;QAAI;OAAM,CAAC;MAAE,CAAC;KACvF,QAAQ,CAAC;KACT,MAAM,aAAa;IACrB;IAEA,cAAc,iBAAiB,eAAe;KAC5C,CAAM,YAAY;MAChB,cAAc,WAAW;MACzB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,OAAO,MAAM,IAAI,cAAc;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACnE,IAAI,MAAM,OAAO,OAAO;QACtB,YAAY,cAAc,QAAQ,MAAM,SAAS;QACjD;OACF;OAGA,aAAa,KAAK,IAAI,IAAI;OAC1B,YAAY,cAAc;OAC1B,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,YAAY,OAAO;QAC7B,YAAY,cAAc,YAAY,QAAQ,UAAU,OAAO;QAC/D;OACF;OACA,IAAI,QAAQ,OACV,YAAY,cAAc,OAAO,UAC7B,0CACA;YAEJ,YAAY,cAAc;OAE5B,MAAM,aAAa;MACrB,SAAS,OAAY;OACnB,YAAY,cAAc,UAAU,OAAO,WAAW;MACxD,UAAU;OACR,cAAc,WAAW;OACzB,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;MAC5C;KACF,EAAA,CAAG;IACL,CAAC;IAED,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,YAAY,cAAc;MAC1B,IAAI;OAEF,MAAM,UAAU,MAAM,eAAe,oBAAoB,GAAG,YAAY;QACtE,MAAM,OAAO,MAAM,IAAI,yBAAyB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QAC9E,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,MAAM,SAAS,UAAU;QACxD,MAAM,EAAE,IAAI,KAAK,GAAG,WAAW;QAC/B,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;OACvC,CAAC;OAED,IAAI,QAAQ,SAAS,SACnB,YAAY,cAAc,OAAO,QAAQ,KAAK;YACzC,IAAI,QAAQ,SAAS,aAC1B,YAAY,cAAc;YACrB;QAEL,MAAM,SAAS,MAAM,IAAI,oBAAoB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QAC3E,MAAM,MAAM,QAAQ,SAAS,gBAAgB,iBAAiB,WAAW,QAAQ,OAAO;QACxF,YAAY,cAAc,QAAQ,KAC9B,GAAG,IAAI,cAAc,OAAO,SAC5B,QAAQ,QAAQ,SAAS;OAC/B;MACF,SAAS,OAAY;OACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;MACtD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAED,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,OAAO,MAAM,aAAa;OAChC,IAAI,CAAC,MAAM;QACT,YAAY,cAAc;QAC1B;OACF;OACA,YAAY,cAAc,QAAQ,KAAK,KAAK;OAC5C,MAAM,SAAS,MAAM,iBAAiB,IAAI;OAC1C,IAAI,OAAO,SAAS,cAAc;QAChC,YAAY,cAAc,QAAQ,OAAO,KAAK,iBAAiB,OAAO,OAAO;QAC7E;OACF;OAGA,MAAM,OAAO,OAAO,SAAS,SAAS,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,SAAS,OAAO,QAAQ;OACxF,MAAM,SAAS,MAAM,IAAI,oBAAoB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,IAAI;OAAE,CAAC;OAC3F,YAAY,cAAc,QAAQ,KAC9B,QAAQ,OAAO,KAAK,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,QAAQ,OAAO,YACjF,QAAQ,QAAQ,SAAS;OAC7B,IAAI,QAAQ,IAAI,MAAM,aAAa;MACrC,SAAS,OAAY;OACnB,YAAY,cAAc,QAAQ,OAAO,WAAW;MACtD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAGD,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,kBAAkB,CAAC;IAC9D,UAAU,OACR,GACE,KACA,YACA,2DACF,CACF;IACA,MAAM,UAAU,GAAG,QAAQ,YAAY,qDAAqD;IAC5F,UAAU,OAAO,OAAO;IACxB,UAAU,OACR,GACE,KACA,YACA,iGACF,CACF;IACA,MAAM,aAAa,GAAG,YAAY,UAAU;IAC5C,WAAW,cAAc;IACzB,UAAU,OAAO,UAAU;IAC3B,MAAM,eAAe,GAAG,OAAO,SAAS;IACxC,aAAa,MAAM,YAAY;IAC/B,MAAM,WAAW,GAAG,UAAU,WAAW,OAAO;IAChD,aAAa,OAAO,QAAQ;IAC5B,UAAU,OAAO,YAAY;IAC7B,gBAAgB,OAAO,SAAS;IAGhC,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,OAAO,CAAC;IAClD,SAAS,OAAO,GAAG,KAAK,YAAY,4BAA4B,CAAC;IACjE,MAAM,cAAc,GAAG,OAAO,SAAS;IACvC,YAAY,MAAM,YAAY;IAC9B,MAAM,cAAc,GAAG,UAAU,WAAW;IAC5C,YAAY,MAAM,WAAW;IAC7B,MAAM,UAAU,GAAG,UAAU,WAAW,MAAM;IAC9C,YAAY,OAAO,aAAa,OAAO;IACvC,SAAS,OAAO,WAAW;IAC3B,MAAM,UAAU,GAAG,OAAO,SAAS;IACnC,QAAQ,MAAM,UAAU;IACxB,SAAS,OAAO,OAAO;IAGvB,MAAM,aAAa,GAAG,OAAO,UAAU;IACvC,WAAW,OAAO,GAAG,OAAO,gBAAgB,YAAY,CAAC;IACzD,MAAM,aAAa,GAAG,MAAM,YAAY;IACxC,WAAW,OAAO,UAAU;IAC5B,MAAM,aAAa,GAAG,KAAK,YAAY,EAAE;IACzC,WAAW,OAAO,UAAU;IAC5B,UAAU,OAAO,YAAY,QAAQ;IAIrC,IAAI,WAAW;IACf,IAAI,WAAW;IACf,IAAI,aAAa;;IAEjB,IAAI,mBAAmB;;IAEvB,IAAI,yBAAyB;;;;;;;IAO7B,IAAI,sBAA2B;IAE/B,MAAM,eAAe;;IAGrB,MAAM,kBAAkB,IAAI,WAAmB;KAC7C,IAAI;MACF,MAAM,MAAM,OAAO,aAAa,QAAQ,YAAY;MACpD,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;MACxC,OAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,KAAK,SAAkB,OAAO,IAAI,CAAC,IAAI,CAAC;KAChF,QAAQ;MAEN,OAAO,CAAC;KACV;IACF,EAAA,CAAG,CAAC;IAEJ,MAAM,+BAAqC;KACzC,IAAI;MACF,OAAO,aAAa,QAAQ,cAAc,KAAK,UAAU,CAAC,GAAG,eAAe,CAAC,CAAC;KAChF,QAAQ,CAER;IACF;IACA,IAAI,kBAAkB;IAEtB,MAAM,YAAY,SAAmC;KACnD,SAAS,cAAc;KACvB,KAAK,MAAM,CAAC,KAAK,UAAU,MACzB,SAAS,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,KAAA,GAAW,KAAK,CAAC;IAEpE;;;;;IAMA,IAAI;IAEJ,MAAM,eAAe,WAAgC;KACnD,WAAW,CAAC,CAAC,OAAO,MAAM;KAC1B,WAAW,CAAC,CAAC,OAAO;KACpB,aAAa,CAAC,CAAC,OAAO;KAItB,MAAM,MAAM,OAAO;KACnB,IAAI,KAAK,aACP,sBAAsB,OAAO,IAAI,WAAW,GAAG,IAAI,YAAY;KAGjE,MAAM,cAAc,WAAY,OAAO,KAAK,aAAa,eAAe,UAAW;KACnF,MAAM,YAAY,aAAa,WAAY,OAAO,KAAK,aAAa,QAAQ,OAAQ;KACpF,IAAI,YAAY,CAAC,OAAO,KAAK,YAAY;MACvC,MAAM,QAAQ,OAAO;MACrB,IAAI,SAAS,CAAC,MAAM,IAAI;OACtB,MAAM,cAAc;OACpB,MAAM,YAAY;MACpB;KACF;KAEA,MAAM,OAA2B,CAAC;KAClC,qBAAqB,OAAO,SAAS,OAAO,MAAM,YAAY,IAAI,OAAO,OAAO,MAAM,YAAY,IAAI;KACtG,WAAW;KACX,kBAAkB,OAAO,OAAO,QAAQ,mBAAmB,CAAC;KAC5D,cAAc;MAAE,SAAS,OAAO,QAAQ;MAAS;KAAgB,CAAC;KAClE,YAAY,OAAO,KAAK;KACxB,KAAK,KAAK,CAAC,UAAU,OAAO,uBAAuB,CAAC,EAAA,CAAG,SAAS,OAAO,QAAQ,IAAI,KAAK,OAAO,SAAS,aAAa,iBAAiB,OAAO,UAAU,CAAC;KACxJ,IAAI,UAAU;MACZ,KAAK,KAAK,CAAC,MAAM,OAAO,KAAK,WAAW,YAAY,CAAC;MACrD,KAAK,KAAK,CAAC,QAAQ,OAAO,KAAK,aAAa,IAAI,KAAK,OAAO,KAAK,UAAU,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC;MAMrG,IADwB,CAAC,OAAO,KAAK,aAAa,CAAC,OAAO,KAAK,gBAE7D,KAAK,KAAK,CAAC,QAAQ,6CAA6C,CAAC;WAEjE,KAAK,KAAK,CAAC,QAAQ,wCAAwC,CAAC;MAE9D,KAAK,KAAK,CACR,UACA,OAAO,KAAK,YACR,UACA,+DACN,CAAC;MACD,IAAI,OAAO,KAAK,WAKd,KAAK,KAAK,CACR,aACA,GAAG,mBAAmB,OAAO,KAAK,UAAU,EAAE,mCAChD,CAAC;MAEH,KAAK,KAAK,CAAC,OAAO,OAAO,KAAK,iBAAiB,gCAAgC,iBAAiB,CAAC;MACjG,KAAK,KAAK,CAAC,YAAY,OAAO,KAAK,YAAY,MAAM,CAAC;MACtD,KAAK,KAAK,CAAC,YAAY,GAAG,OAAO,KAAK,eAAe,EAAE,IAAI,CAAC;MAC5D,IAAI,OAAO,YAAY,KAAK,KAAK,CAAC,SAAS,OAAO,WAAW,KAAK,OAAO,MAAM,OAAO,WAAW,SAAS,IAAI,CAAC;MAI/G,MAAM,aAAa,OAAO,MAAM;MAChC,MAAM,cAAc,OAAO,MAAM;MACjC,IAAI,aACF,KAAK,KAAK,CACR,SACA,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,YAAY,QAAQ,eACxD,CAAC;WACI,IAAI,YACT,KAAK,KAAK,CAAC,SAAS,KAAK,QAAQ,UAAU,EAAE,eAAe,CAAC;MAG/D,MAAM,WAAW,OAAO,QAAQ;MAChC,IAAI,aAAa,KAAA,GACf,KAAK,KAAK,CACR,QACA,OAAO,QAAQ,kBACX,gBAAgB,SAAS,wBACzB,kBAAkB,SAAS,GACjC,CAAC;KAEL,OAAO;MAGL,MAAM,YAAY,OAAO;MACzB,KAAK,KAAK,CACR,QACA,YACI,UAAU,gBACR,+BACA,UAAU,UACR,YAAY,UAAU,QAAQ,mBAC9B,+CACJ,WACE,YACA,4BACR,CAAC;KACH;KACA,IAAI,OAAO,iBACT,KAAK,KAAK,CAAC,QAAQ,GAAG,OAAO,gBAAgB,QAAQ,KAAK,IAAI,KAAK,OAAO,gBAAgB,EAAE,CAAC,CAAC,mBAAmB,GAAG,CAAC;KAEvH,IAAI,OAAO,mBAAmB,OAAO,eAAe,UAAU;MAC5D,MAAM,WAAW,OAAO,cAAc;MACtC,KAAK,KAAK,CACR,QACA,SAAS,SAAS,QAAQ,MAAM,IAAI,YAAY,SAAS,SAAS,MAAM,IAAI,QAAQ,SAAS,cAAc,MAAM,KACnH,CAAC;KACH;KACA,IAAI,OAAO,eAAe,aAAa,OAAO,iBAC5C,KAAK,KAAK,CAAC,QAAQ,OAAO,cAAc,SAAS,CAAC;KAEpD,IAAI,OAAO,eAAe,OAAO,YAAY,SAAS,SAAS,GAC7D,KAAK,KAAK,CAAC,QAAQ,OAAO,OAAO,YAAY,SAAS,OAAO,gBAAgB,OAAO,YAAY,SAAS,KAAK,IAAI,GAAG,CAAC;UACjH,IAAI,OAAO,iBAChB,KAAK,KAAK,CAAC,QAAQ,mBAAmB,CAAC;KAEzC,IAAI,OAAO,aAAa,QAAQ;MAC9B,MAAM,MAAM,YAAY,KAAK,OAAO,YAAY,MAAM;MACtD,KAAK,KAAK,CAAC,WAAW,GAAG,MAAM,oBAAoB,OAAO,OAAO,YAAY,QAAQ,CAAC;KACxF;KACA,IAAI,OAAO,aAAa,YAAY,QAAQ;MAC1C,MAAM,QAAQ,OAAO,YAAY,WAAW,QAAQ,MAAM,gBAAgB,KAAK,CAAC,CAAC;MACjF,KAAK,KAAK,CAAC,QAAQ,GAAG,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,MAAM,OAAO,OAAO,YAAY,WAAW,KAAK,IAAI,GAAG,CAAC;KACpH;KACA,IAAI,OAAO,aAAa,kBAAkB,KAAA,GACxC,KAAK,KAAK,CAAC,aAAa,OAAO,YAAY,gBAAgB,mBAAmB,SAAS,CAAC;KAM1F,MAAM,aAAa,OAAO;KAC1B,IAAI,YAAY;MACd,MAAM,OAAO,WAAW,gBACpB,mBACA,WAAW,UACT,WAAW,WAAW,YAAY,gBAClC,WAAW,WAAW,YAAY;MACxC,KAAK,KAAK,CAAC,QAAQ,GAAG,WAAW,YAAY,KAAK,MAAM,CAAC;KAC3D;KACA,SAAS,IAAI;KAEb,IAAI,CAAC,WAAW,QAAQ,MAAM,WAAW,WAAW;KACpD,IAAI,YAAY;MACd,WAAW,cAAc,WAAW,gBAAgB,YAAY,WAAW,UAAU,KAAK,WAAW,QAAQ,OAAO;MACpH,WAAW,WAAW,CAAC,WAAW,iBAAiB,CAAC,WAAW;MAC/D,WAAW,QAAQ,WAAW,gBAC1B,mBACA,WAAW,UACT,SAAS,WAAW,QAAQ,kCAC5B;KACR,OAAO;MACL,WAAW,WAAW,CAAC;MACvB,WAAW,cAAc,aAAa,YAAY;MAClD,WAAW,QAAQ,WAAW,KAAK;KACrC;KAGA,YAAY,cAAc;KAC1B,IAAI,UAAU;MACZ,YAAY,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,GAAG,OAAO,KAAA,GAAW,OAAO,KAAK,WAAW,YAAY,CAAC;MAClG,YAAY,OACV,GAAG,OAAO,KAAK,MAAM,GACrB,GAAG,OAAO,KAAA,GAAW,OAAO,KAAK,aAAa,IAAI,KAAK,OAAO,KAAK,UAAU,CAAC,CAAC,eAAe,IAAI,IAAI,CACxG;KACF,OACE,YAAY,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,GAAG,OAAO,KAAA,GAAW,eAAe,CAAC;KAEhF,UAAU,WAAW,CAAC;KACtB,UAAU,WAAW,CAAC,YAAY,CAAC;KACnC,UAAU,QAAQ,WAAW,KAAK;KAGlC,IAAI,YAAY,QAAQ,WAAW,OAAO,OAAO,QAAQ;MACvD,YAAY,cAAc;MAC1B,KAAK,MAAM,SAAS,OAAO,QAAQ;OACjC,MAAM,SAAS,SAAS,cAAc,QAAQ;OAC9C,OAAO,QAAQ,MAAM;OACrB,OAAO,cAAc,MAAM;OAC3B,YAAY,OAAO,MAAM;MAC3B;KACF;KAEA,WAAW,cAAc;KACzB,KAAK,MAAM,SAAS,OAAO,QAAQ;MACjC,MAAM,OAAO,GAAG,IAAI;MACpB,KAAK,OAAO,GAAG,OAAO,QAAQ,MAAM,IAAI,CAAC;MACzC,MAAM,WAAW,MAAM,iBAAiB,UACpC,IAAI,MAAM,gBAAgB,QAAA,CAAW,QAAQ,CAAC,EAAE,KAChD,GAAG,KAAK,MAAM,MAAM,gBAAgB,IAAI,EAAE;MAC9C,KAAK,OAAO,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,cAAc,MAAM,WAAW,MAAM,IAAI,SAAS,SAAS,WAAW,CAAC;MAC/G,KAAK,OAAO,GAAG,OAAO,MAAM,MAAM,WAAW,CAAC;MAC9C,WAAW,OAAO,IAAI;KACxB;KACA,WAAW,cACT,2FAEe,OAAO,QAAQ,kBAAkB,EAAE;IAEtD;IAEA,MAAM,UAAU,OAAO,QAAQ,SAAwB;KACrD,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,UAAU,QAAQ,aAAa,IAAI;MAC5D,IAAI,UAAU;MACd,YAAY,MAAM;KACpB,SAAS,OAAY;MACnB,YAAY,UAAU,OAAO,WAAW,SAAS,KAAK;KACxD;IACF;IAKA,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,WAAW,CAAC;IAEtD,MAAM,mBAAqC;KACzC,MAAM,QAAQ,GAAG,SAAS,WAAW;KACrC,MAAM,OAAO;KACb,MAAM,MAAM;KACZ,MAAM,MAAM;KACZ,MAAM,OAAO;KACb,OAAO;IACT;IAEA,MAAM,UAAU,GAAG,OAAO,cAAc;IACxC,MAAM,YAAY,GAAG,SAAS,YAAY;IAC1C,MAAM,YAAY,GAAG,OAAO;IAC5B,UAAU,OAAO;IACjB,MAAM,YAAY,GAAG,QAAQ,kBAAkB;IAC/C,UAAU,OAAO,WAAW,WAAW,GAAG,QAAQ,KAAA,GAAW,iBAAiB,CAAC;IAC/E,QAAQ,OAAO,SAAS;IACxB,SAAS,OAAO,OAAO;IAEvB,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,WAAW;IAC5B,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IAEtB,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,WAAW;IAC5B,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IAEtB,MAAM,eAAe,GAAG,KAAK,YAAY,EAAE;IAC3C,SAAS,OAAO,YAAY;IAE5B,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,SAAS,OAAO,SAAS;IAMzB,MAAM,YAAY,GAAG,OAAO,cAAc;IAC1C,UAAU,OAAO,GAAG,QAAQ,kBAAkB,WAAW,CAAC;IAC1D,MAAM,cAAc,GAAG,SAAS,WAAW;IAC3C,YAAY,OAAO;IACnB,YAAY,aAAa,cAAc,aAAa;IACpD,YAAY,QAAQ;IACpB,MAAM,cAAc,GAAG,QAAQ,kBAAkB,GAAG;IACpD,UAAU,OAAO,aAAa,WAAW;IACzC,SAAS,OAAO,SAAS;IACzB,MAAM,aAAa,GAAG,KAAK,YAAY,EAAE;IACzC,SAAS,OAAO,UAAU;;IAG1B,MAAM,YAAY,MAAsB,GAAG,OAAO,SAAS,CAAC,KAAK,IAAI,IAAA,CAAQ,QAAQ,IAAI,QAAW,IAAI,IAAI,CAAC,IAAI,IAAI;IAErH,MAAM,uBAA6B;KACjC,MAAM,QAAQ,OAAO,YAAY,KAAK;KACtC,YAAY,cAAc,SAAS,KAAK;KACxC,MAAM,QAAQ,SAAS,OAAY,OAAO,SAAS,MAAU,OAAO;KACpE,WAAW,cACT,YAAY,SAAS,KAAK,EAAE,GAAG,MAAM;IAIzC;IAGA,MAAM,YAAY,GAAG,KAAK,YAAY,EAAE;IACxC,UAAU,cACR;IAGF,SAAS,OAAO,SAAS;IAEzB,YAAY,iBAAiB,SAAS,cAAc;IACpD,YAAY,iBAAiB,gBAAgB;KAC3C,eAAe;KACf,SAAc,EAAE,gBAAgB,OAAO,YAAY,KAAK,EAAE,CAAC;IAC7D,CAAC;IAOD,MAAM,SAAS,GAAG,OAAO,cAAc;IACvC,OAAO,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IAClD,MAAM,WAAW,GAAG,SAAS,WAAW;IACxC,SAAS,OAAO;IAChB,SAAS,aAAa,cAAc,aAAa;IACjD,SAAS,QAAQ;IACjB,MAAM,WAAW,GAAG,QAAQ,kBAAkB,GAAG;IACjD,OAAO,OAAO,UAAU,QAAQ;IAChC,SAAS,OAAO,MAAM;IACtB,MAAM,UAAU,GAAG,KAAK,YAAY,EAAE;IACtC,SAAS,OAAO,OAAO;IAEvB,MAAM,sBAA4B;KAChC,MAAM,QAAQ,OAAO,SAAS,KAAK;KACnC,SAAS,cAAc,UAAU,IAAI,QAAQ,GAAG,MAAM;KACtD,QAAQ,cACN,UAAU,IACN,gEACA,cAAc,MAAM;IAE5B;IACA,SAAS,iBAAiB,SAAS,aAAa;IAChD,SAAS,iBAAiB,gBAAgB;KACxC,cAAc;KACd,SAAc,EAAE,cAAc,OAAO,SAAS,KAAK,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,kBAAkB,MAAM,CAAC;IACtD,MAAM,cAAiD,CAAC;IACxD,KAAK,MAAM,QAAQ;KACjB,CAAC,aAAa,IAAI;KAClB,CAAC,YAAY,QAAQ;KACrB,CAAC,QAAQ,IAAI;IACf,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,SAAS,EAAE,gBAAgB,IAAI,CAAC,CAAC;KAC1E,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;IACvB;IACA,SAAS,OAAO,UAAU;IAC1B,MAAM,cAAc,GAAG,KAAK,YAAY,EAAE;IAC1C,SAAS,OAAO,WAAW;IAM3B,MAAM,gBAAgB,GAAG,KAAK;IAC9B,cAAc,MAAM,UAAU;IAC9B,SAAS,OAAO,aAAa;;IAG7B,MAAM,aACJ,OACA,QACA,MACA,QACA,WACgE;KAChE,MAAM,MAAM,GAAG,OAAO,cAAc;KACpC,IAAI,OAAO,GAAG,QAAQ,kBAAkB,KAAK,CAAC;KAC9C,MAAM,KAAK,GAAG,SAAS,WAAW;KAClC,MAAM,KAAK,GAAG,SAAS,WAAW;KAClC,KAAK,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAY;MAC9D,MAAM,OAAO;MACb,MAAM,MAAM,OAAO,OAAO,GAAG;MAC7B,MAAM,MAAM,OAAO,OAAO,GAAG;MAC7B,MAAM,OAAO;MACb,MAAM,aAAa,cAAc,GAAG,QAAQ,OAAO;MACnD,MAAM,QAAQ,GAAG,QAAQ;KAC3B;KACA,MAAM,QAAQ,GAAG,QAAQ,uBAAuB,GAAG;KACnD,MAAM,cAAoB;MACxB,MAAM,cAAc,OAAO,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;KAC/D;KACA,MAAM,gBAAsB;MAG1B,IAAI,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,KAAK,GAAG;OACvC,MAAM,OAAO,GAAG;OAChB,GAAG,QAAQ,GAAG;OACd,GAAG,QAAQ;MACb;MACA,MAAM;KACR;KACA,MAAM,iBAAuB,OAAO,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;KACtE,GAAG,iBAAiB,SAAS,OAAO;KACpC,GAAG,iBAAiB,SAAS,OAAO;KACpC,GAAG,iBAAiB,UAAU,QAAQ;KACtC,GAAG,iBAAiB,UAAU,QAAQ;KACtC,MAAM,OAAO,GAAG,OAAO,gBAAgB;KACvC,KAAK,OAAO,IAAI,EAAE;KAClB,IAAI,OAAO,MAAM,KAAK;KACtB,OAAO;MACL;MACA,MAAM,KAAK,QAAQ;OACjB,GAAG,QAAQ,OAAO,GAAG;OACrB,GAAG,QAAQ,OAAO,GAAG;OACrB,MAAM;MACR;KACF;IACF;IAEA,MAAM,YAAY,UAChB,QACA;KAAE,KAAK;KAAG,KAAK;IAAG,GAClB,MACC,IAAI,OAAO,GAAG,GAAG,GAAG,GAAG,MACvB,IAAI,OAAO,KAAK,SAAS,EAAE,cAAc;KAAE,KAAK;KAAI,KAAK;IAAG,EAAE,CAAC,CAClE;IACA,MAAM,YAAY,UAChB,QACA;KAAE,KAAK;KAAG,KAAK;IAAI,GACnB,MACC,IAAI,OAAO,GAAG,GAAG,GAAG,GAAG,MACvB,IAAI,OAAO,KAAK,SAAS,EAAE,gBAAgB;KAAE,KAAK,KAAK,MAAM,KAAK,GAAI;KAAG,KAAK,KAAK,MAAM,KAAK,GAAI;IAAE,EAAE,CAAC,CAC1G;IACA,MAAM,UAAU,UACd,QACA;KAAE,KAAK;KAAG,KAAK;IAAG,GAClB,QACC,IAAI,OAAQ,MAAM,IAAI,QAAQ,GAAG,GAAG,QAAQ,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,EAAE,MAChE,IAAI,OAAO,KAAK,SAAS,EAAE,cAAc;KAAE,KAAK,KAAK,MAAM,KAAK,GAAI;KAAG,KAAK,KAAK,MAAM,KAAK,GAAI;IAAE,EAAE,CAAC,CACxG;IACA,cAAc,OAAO,UAAU,KAAK,UAAU,KAAK,QAAQ,GAAG;IAC9D,cAAc,OACZ,GACE,KACA,YACA,oFAEF,CACF;IACA,cAAc,OACZ,GACE,KACA,YACA,8FAEF,CACF;IAEA,SAAS,OACP,GACE,KACA,YACA,8EAEF,CACF;IACA,MAAM,UAAU,GAAG,KAAK,yBAAyB,EAAE;IACnD,SAAS,OAAO,OAAO;IACvB,SAAS,OAAO,QAAQ;IAKxB,MAAM,aAAa,GAAG,OAAO,UAAU;IACvC,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,QAAQ,MAAM,CAAC;IAC5C,MAAM,cAAc,GAAG,QAAQ,iBAAiB,SAAS;IACzD,WAAW,OAAO,WAAW;IAC7B,WAAW,OAAO,UAAU;IAC5B,WAAW,OACT,GAAG,KAAK,YAAY,0CAA0C,CAChE;IACA,MAAM,WAAW,GAAG,OAAO,QAAQ;IACnC,WAAW,OAAO,QAAQ;IAC1B,MAAM,iBAAiB,GAAG,KAAK;IAC/B,WAAW,OAAO,cAAc;IAChC,MAAM,YAAY,GAAG,OAAO,SAAS;IACrC,MAAM,mBAAmB,GAAG,UAAU,iBAAiB,MAAM;IAC7D,UAAU,OAAO,gBAAgB;IACjC,WAAW,OAAO,SAAS;IAC3B,MAAM,YAAY,GAAG,KAAK,yBAAyB,EAAE;IACrD,WAAW,OAAO,SAAS;IAC3B,SAAS,OAAO,UAAU;;IAG1B,MAAM,YAAY,QAAkB,gBAAqC;KACvE,MAAM,SAAS,OAAO,SAAS,SAAS,CAAC,CAAC;KAC1C,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,MAAM;KACjC,MAAM,OAAO,MAAM,OAAO;KAC1B,IAAI,MAAM;KACV,OAAO,SAAS,OAAO,UAAU;MAC/B,MAAM,SAAU,QAAQ,MAAO;MAE/B,MAAM,MAAM,YAAY,IAAI,KAAK,IAAI,mBAAiB;MACtD,OAAO,QAAQ,IAAI,OAAO,QAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE,QAAQ,MAAM,OAAA,CAAQ,QAAQ,CAAC,EAAE,YAAY,OAAO,GAAA,CAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,IAAI,QAAQ,QAAQ,IAAI,IAAI,EAAG,CAAC,CAAC,QAAQ,CAAC,EAAE;KACtL,CAAC;KACD,OAAO;KACP,OAAO;IACT;IAEA,MAAM,SAAS,OAAwB,MAAM,MAAS,IAAI,KAAK,IAAA,CAAQ,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAA,CAAM,QAAQ,CAAC,EAAE;IAElH,MAAM,gBAAgB,SAAoB;KACxC,MAAM,QAAQ,OAAO,MAAM,SAAS,CAAC;KACrC,YAAY,cAAc,KAAK,MAAM,SAAS,GAAG;KACjD,YAAY,YAAY,aAAa,QAAQ,IAAI,OAAO;KACxD,SAAS,cAAc;KACvB,eAAe,cAAc;KAC7B,IAAI,UAAU,GAAG;MACf,UAAU,cAAc;MACxB;KACF;KACA,MAAM,QAAQ,KAAa,UAAwB;MACjD,SAAS,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,KAAA,GAAW,KAAK,CAAC;KAClE;KACA,KAAK,QAAQ,GAAG,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,KAAK,UAAU,EAAE,EAAE;KAC3E,MAAM,OAAO,KAAK;KAClB,KACE,UACA,OACI,MAAM,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,KAAK,GAAG,EAAE,QAAQ,MAAM,KAAK,GAAG,EAAE,MAAM,KAAK,QAAQ,KAC1F,MACN;KACA,IAAI,QAAQ,KAAK,MAAM,MACrB,UAAU,cAAc,UAAU,MAAM,KAAK,GAAG,EAAE;KAEpD,MAAM,WAAW,KAAK,YAAY,CAAC;KACnC,KACE,QACA,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO,QAAQ,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,IAAI,GACjH;KACA,KAAK,QAAQ,GAAG,KAAK,WAAW,SAAS,EAAE,SAAS,KAAK,OAAO,KAAK,WAAW,SAAS,KAAK,IAAI,EAAE,IAAI;KAExG,MAAM,YAAmB,MAAM,QAAQ,KAAK,SAAS,IAAI,KAAK,YAAY,CAAC;KAC3E,IAAI,UAAU,SAAS,GAAG;MACxB,MAAM,UAAU,OAAuB;OAErC,MAAM,OADe,MAAM,QAAQ,qBAAqB,QAAQ,IAAI,oBAAoB,WAAW,CAAC,EAAA,CAClF,MAAM,SAAS,KAAK,OAAO,EAAE;OAC/C,OAAO,MAAM,OAAO,IAAI,SAAS,IAAI,WAAW,IAAI,EAAE,IAAI;MAC5D;MACA,KACE,OACA,UACG,MAAM,GAAG,OAAO,OAAO,GAAG,KAAK,KAAK,MAAM,OAAO,GAAG,KAAK,KAAK,EAAE,CAAC,CACjE,KAAK,QAAQ,GAAG,OAAO,IAAI,SAAS,EAAE,GAAG,IAAI,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC,CACzE,KAAK,KAAK,CACf;KACF;KACA,MAAM,SAAmB,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;KAErE,MAAM,8BAAc,IAAI,IAAY;KAEpC,CAD+B,MAAM,QAAQ,KAAK,YAAY,IAAI,KAAK,eAAe,CAAC,EAAA,CAC1E,SAAS,OAAO,UAAU;MACrC,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK;KACtC,CAAC;KACD,eAAe,YAAY,SAAS,QAAQ,WAAW;KACvD,eAAe,OAAO,GAAG,KAAK,YAAY,wBAAwB,KAAK,IAAI,GAAG,GAAG,MAAM,EAAE,IAAI,CAAC;IAChG;IAEA,MAAM,aAAa,YAA2B;KAC5C,IAAI;MACF,aAAa,MAAM,IAAI,kBAAkB,CAAC;KAC5C,SAAS,OAAY;MACnB,UAAU,cAAc,UAAU,OAAO,WAAW;KACtD;IACF;IACA,iBAAiB,iBAAiB,eAAe,KAAK,WAAW,CAAC;IAMlE,MAAM,gBAAgB,GAAG,OAAO,UAAU;IAC1C,cAAc,OAAO,GAAG,OAAO,gBAAgB,SAAS,CAAC;IAEzD,MAAM,eAAe,GAAG,OAAO,cAAc;IAC7C,aAAa,OAAO,GAAG,QAAQ,kBAAkB,QAAQ,CAAC;IAC1D,MAAM,gBAAmD,CAAC;IAC1D,KAAK,MAAM,QAAQ,CACjB,CAAC,YAAY,kBAAkB,GAC/B,CAAC,QAAQ,MAAM,CACjB,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,cAAc,GAAG,CAAC;KAC3D,cAAc,OAAO;KACrB,aAAa,OAAO,GAAG;IACzB;IACA,cAAc,OAAO,YAAY;IAEjC,MAAM,gBAAgB,GAAG,KAAK,YAAY,EAAE;IAC5C,cAAc,OAAO,aAAa;IAClC,MAAM,qBAAqB,GAAG,KAAK,YAAY,EAAE;IACjD,cAAc,OAAO,kBAAkB;IAEvC,MAAM,UAAU,GAAG,OAAO,cAAc;IACxC,MAAM,mBAAmB,GAAG,UAAU,iBAAiB,YAAY;IACnE,QAAQ,OAAO,gBAAgB;IAC/B,cAAc,OAAO,OAAO;IAE5B,MAAM,mBAAmB,GAAG,OAAO,WAAW,EAAE;IAChD,iBAAiB,MAAM,UAAU;IACjC,cAAc,OAAO,gBAAgB;IAErC,MAAM,eAAe,cAAc;IACnC,cAAc,OAAO,aAAa,IAAI;IACtC,cAAc,OAAO,aAAa;IAQlC,MAAM,cAAc,GAAG,OAAO,UAAU;IACxC,YAAY,OAAO,GAAG,OAAO,gBAAgB,SAAS,CAAC;IACvD,MAAM,aAAa,GAAG,OAAO,cAAc;IAC3C,WAAW,OAAO,GAAG,QAAQ,kBAAkB,OAAO,CAAC;IACvD,MAAM,cAAiD,CAAC;IACxD,KAAK,MAAM,QAAQ,CACjB,CAAC,QAAQ,UAAU,GACnB,CAAC,WAAW,YAAY,CAC1B,GAAY;KACV,MAAM,MAAM,KAAK;KACjB,MAAM,MAAM,GAAG,UAAU,4BAA4B,KAAK,EAAE;KAC5D,IAAI,iBAAiB,eAAe,KAAK,gBAAgB,GAAG,CAAC;KAC7D,YAAY,OAAO;KACnB,WAAW,OAAO,GAAG;IACvB;IACA,YAAY,OAAO,UAAU;IAC7B,MAAM,gBAAgB,GAAG,KAAK,YAAY,EAAE;IAC5C,YAAY,OAAO,aAAa;IAChC,MAAM,kBAAkB,GAAG,KAAK,YAAY,EAAE;IAC9C,YAAY,OAAO,eAAe;IAClC,MAAM,aAAa,cAAc;IACjC,YAAY,OAAO,WAAW,IAAI;IAClC,YAAY,OAAO,WAAW;IAE9B,uBAAuB,SAAiB,UAAqB;KAC3D,MAAM,OAAO,YAAY,YAAY,YAAY;KACjD,KAAK,MAAM,OAAO,OAAO,KAAK,WAAW,GACvC,YAAY,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,IAAI;KAE1D,IAAI,OACF,cAAc,cACZ,iBAAiB,OAAO,MAAM,aAAa,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,QACnD,MAAM,MAAM,SAAS,MAAM;UAErC,cAAc,cACZ,SAAS,YACL,qCACA;IAEV;IAEA,MAAM,oBAAoB,SAAoB;KAC5C,sBAAsB,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;KAC/D,MAAM,OAAO,OAAO,MAAM,QAAQ,EAAE;KAEpC,MAAM,OAAO,OAAO,MAAM,gBAAgB,EAAE;KAC5C,gBAAgB,cAAc,OAAO,GAAG,KAAK,SAAS,SAAS;IACjE;IAEA,MAAM,kBAAkB,OAAO,SAA4C;KACzE,WAAW,cAAc;KACzB,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,iBAAiB;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;MAAE,CAAC;MAC5F,IAAI,QAAQ,IAAI;OACd,iBAAiB,MAAM;OACvB,WAAW,cACT,MAAM,OAAO,SAAS,YAAY,SAAS,OAAO,UACjD,OAAO,cAAc,QAClB,0BACA;MACR,OACE,WAAW,cAAc,QAAQ,QAAQ,SAAS;KAEtD,SAAS,OAAY;MACnB,WAAW,cAAc,QAAQ,OAAO,WAAW;KACrD;IACF;IAKA,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,OAAO,CAAC;IACnD,MAAM,YAAY,GAAG,OAAO,QAAQ;IACpC,UAAU,OAAO,SAAS;IAC1B,MAAM,YAAY,GAAG,OAAO,SAAS;IACrC,UAAU,MAAM,YAAY;IAC5B,MAAM,YAAY,GAAG,UAAU,iBAAiB,MAAM;IACtD,UAAU,OAAO,SAAS;IAC1B,UAAU,OAAO,SAAS;IAC1B,MAAM,YAAY,GAAG,KAAK,yBAAyB,EAAE;IACrD,UAAU,OAAO,SAAS;IAC1B,UAAU,OACR,GAAG,KAAK,YAAY,qDAAmD,CACzE;IACA,UAAU,OAAO,SAAS;IAE1B,MAAM,iBAAiB,SAAoB;KACzC,UAAU,cAAc;KACxB,UAAU,OAAO,GAAG,OAAO,KAAK,MAAM,GAAG,GAAG,OAAO,KAAA,GAAW,MAAM,WAAW,IAAI,CAAC;KACpF,UAAU,OACR,GAAG,OAAO,KAAK,OAAO,GACtB,GACE,OACA,KAAA,GACA,MAAM,kBAAkB,IACpB,KAAK,KAAK,MAAM,KAAK,kBAAkB,GAAM,EAAE,iBAC/C,KACN,CACF;IACF;IAEA,UAAU,iBAAiB,eAAe;KACxC,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,UAAU,cAAc;MACxB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,iBAAiB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACxE,IAAI,CAAC,QAAQ,IAAI;QACf,UAAU,cAAc,QAAQ,QAAQ,SAAS;QACjD;OACF;OACA,MAAM,UAAU,OAAO;OACvB,cAAc;QAAE;QAAS;OAAgB,CAAC;OAC1C,IAAI,OAAO,WACT,UAAU,cAAc,SAAS,OAAO,OAAO,MAAM,QAAQ,GAC3D,OAAO,cAAc,MAAM,UAAU,OAAO,WAAW,EAAE,OAAO,KAC/D,OAAO,MAAM,MAAM,OAAO,QAAQ;YAErC,UAAU,cAAc,UAAU,QAAQ;MAE9C,SAAS,OAAY;OACnB,UAAU,cAAc,QAAQ,OAAO,WAAW;MACpD,UAAU;OACR,UAAU,WAAW;MACvB;KACF,EAAA,CAAG;IACL,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,UAAU;IACtC,UAAU,OAAO,GAAG,OAAO,gBAAgB,MAAM,CAAC;IAClD,UAAU,OACR,GAAG,KAAK,YAAY,0DAA0D,CAChF;IACA,MAAM,UAAU,GAAG,OAAO,QAAQ;IAClC,UAAU,OAAO,OAAO;IACxB,UAAU,OACR,GAAG,KAAK,YAAY,mDAAmD,CACzE;IACA,UAAU,OAAO,SAAS;IAE1B,MAAM,eAAe,UAAqB;KACxC,QAAQ,cAAc;KACtB,MAAM,OAA2B;MAC/B,CAAC,OAAO,OAAO,YAAY,MAAM;MACjC,CAAC,QAAQ,OAAO,WAAW,GAAG,MAAM,SAAS,kBAAkB,MAAM;MACrE,CAAC,QAAQ,OAAO,UAAU,MAAM;MAChC,CAAC,QAAQ,OAAO,WAAW,GAAG,MAAM,SAAS,aAAa,MAAM;KAClE;KACA,KAAK,MAAM,CAAC,KAAK,UAAU,MACzB,QAAQ,OAAO,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,OAAO,YAAY,KAAK,CAAC;IAEpE;IAEA,MAAM,WAAW,GAAG,OAAO,UAAU;IACrC,SAAS,OAAO,GAAG,OAAO,gBAAgB,aAAa,CAAC;IACxD,SAAS,OACP,GACE,KACA,YACA,mDACF,CACF;IACA,SAAS,OACP,GACE,KACA,YACA,yHAGF,CACF;IACA,SAAS,OACP,GACE,KACA,YACA,oIAGF,CACF;IACA,UAAU,OAAO,QAAQ;IAEzB,MAAM,sBAAsB,SAAoB;KAC9C,IAAI,CAAC,MAAM;KACX,MAAM,YAAY,OAAO,KAAK,aAAa,KAAK,aAAa,UAAU;KACvE,MAAM,YAAY,OAAO,KAAK,aAAa,SAAS;KACpD,KAAK,MAAM,OAAO,OAAO,KAAK,aAAa,GACzC,cAAc,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,SAAS;KAGjE,MAAM,YAAY,KAAK,sBAAsB;KAC7C,cAAc,SAAS,WAAW,CAAC;KACnC,cAAc,SAAS,QAAQ,YAC3B,KACA;KAEJ,MAAM,QAAQ,CACZ,cAAc,aAAa,sBAAsB,iBACnD;KACA,IAAI,KAAK,UAAU,MAAM,KAAK,iCAAiC;KAC/D,IAAI,cAAc,YAAY,MAAM,KAAK,kCAAkC;KAC3E,cAAc,cAAc,MAAM,KAAK,KAAK;KAC5C,mBAAmB,cAAc,OAAO,KAAK,QAAQ,EAAE;IACzD;IAEA,MAAM,gBAAgB,OAAO,SAA6C;KACxE,aAAa,cAAc;KAC3B,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,cAAc;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;MAAE,CAAC;MACpG,IAAI,QAAQ,IAAI;OACd,mBAAmB,MAAM;OACzB,aAAa,cACX,QAAQ,OAAO,cAAc,aAAa,iBAAiB,aAAa,UACvE,OAAO,cAAc,QAClB,0BACA;MACR,OACE,aAAa,cAAc,QAAQ,QAAQ,SAAS;KAExD,SAAS,OAAY;MACnB,aAAa,cAAc,QAAQ,OAAO,WAAW;KACvD;IACF;IAEA,iBAAiB,iBAAiB,eAAe;KAC/C,CAAM,YAAY;MAChB,iBAAiB,WAAW;MAC5B,iBAAiB,MAAM,UAAU;MACjC,iBAAiB,cAAc;MAC/B,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,0BAA0B;QACjD,QAAQ;QACR,MAAM,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;OACxC,CAAC;OACD,MAAM,QAAkB,CAAC;OACzB,KAAK,MAAM,QAAQ,QAAQ,WAAW,CAAC,GAAG;QACxC,MAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE;QACpC,IAAI,MAAM,WAAW,GAAG,GACtB,MAAM,KAAK,aAAa,KAAK,OAAO,IAAI,SAAS,KAAK,gBAAgB,KAAK;aACtE,IAAI,MAAM,WAAW,GAAG,GAC7B,MAAM,KACJ,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO,KAAK,UAAU,EAAE,UAAU,KAAK,eAAe,OAAO,OAC/F;aACK,IAAI,MAAM,WAAW,GAAG,GAC7B,MAAM,KAAK,cAAc,KAAK,UAAU,IAAI,IAAI,KAAK,KAAK,OAAQ,KAAK,SAAS,OAAQ;OAE5F;OACA,iBAAiB,cAAc,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI,KAAK,UAAU,MAAM;MACxF,SAAS,OAAY;OACnB,iBAAiB,cAAc,QAAQ,OAAO,WAAW;MAC3D,UAAU;OACR,iBAAiB,WAAW;MAC9B;KACF,EAAA,CAAG;IACL,CAAC;IAED,CAAM,YAAY;KAChB,IAAI;MACF,mBAAmB,MAAM,IAAI,YAAY,CAAC;KAC5C,QAAQ;MACN,aAAa,cAAc;KAC7B;KACA,IAAI;MACF,iBAAiB,MAAM,IAAI,eAAe,CAAC;KAC7C,QAAQ;MACN,WAAW,cAAc;KAC3B;IACF,EAAA,CAAG;IAGH,aAAkB;IAElB,WAAgB;IAEhB,QAAa,IAAI;;IAGjB,MAAM,aAAa,MAAiB;KAClC,IAAI,CAAC,GAAG;KACR,UAAU,UAAU,CAAC,CAAC,EAAE;KAExB,MAAM,MAAM,OAAO,EAAE,iBAAiB,GAAK;KAC3C,SAAS,MAAM;KACf,SAAS,MAAM;KACf,MAAM,KAAK,OAAO,EAAE,wBAAwB,GAAI;KAChD,MAAM,KAAK,OAAO,EAAE,wBAAwB,EAAE;KAC9C,SAAS,QAAQ,OAAO,EAAE;KAC1B,SAAS,QAAQ,OAAO,EAAE;KAC1B,SAAS,cAAc,GAAG,GAAG;KAC7B,SAAS,cAAc,GAAG,GAAG;KAC7B,aAAa,cACX,OAAO,KACH,QAAQ,GAAG,6CACX,SAAS,GAAG,GAAG,GAAG,gBAAgB,KAAK,OAAO,KAAK,MAAM,CAAC,EAAE;KAElE,UAAU,cAAc;KACxB,UAAU,OAAO,GAAG,QAAQ,kBAAkB,IAAI,CAAC;KACnD,MAAM,UACJ,EAAE,WAAW;MAAC;OAAE,KAAK;OAAO,KAAK;MAAM;MAAG;OAAE,KAAK;OAAO,KAAK;MAAM;MAAG;OAAE,KAAK;OAAO,KAAK;MAAM;KAAC;KAClG,KAAK,MAAM,UAAU,SAAS;MAC5B,MAAM,YACJ,OAAO,SAAS,EAAE,wBAAwB,QAAU,OAAO,SAAS,EAAE,wBAAwB;MAChG,MAAM,MAAM,GACV,UACA,4BACA,GAAG,OAAO,IAAI,GAAG,OAAO,IAAI,IAAI,YAAY,SAAS,IACvD;MACA,IAAI,iBAAiB,eACnB,KAAK,SAAS;OAAE,sBAAsB,OAAO;OAAK,sBAAsB,OAAO;MAAI,CAAC,CACtF;MACA,IAAI,aAAa,EAAE,yBAAyB,OAAO,OAAO,EAAE,yBAAyB,OAAO,KAC1F,IAAI,UAAU,IAAI,QAAQ;MAE5B,UAAU,OAAO,GAAG;KACtB;KAEA,MAAM,YAAY,EAAE,wBAAwB;MAAE,KAAK;MAAS,KAAK;KAAU;KAC3E,YAAY,MAAM,OAAO,UAAU,GAAG;KACtC,YAAY,MAAM,OAAO,UAAU,GAAG;KACtC,YAAY,OAAO;KACnB,YAAY,QAAQ,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,UAAU,GAAG;KACvF,eAAe;KAEf,MAAM,YAAY,EAAE,sBAAsB;MAAE,KAAK;MAAG,KAAK;KAAI;KAC7D,SAAS,MAAM,OAAO,UAAU,GAAG;KACnC,SAAS,MAAM,OAAO,UAAU,GAAG;KACnC,SAAS,OAAO;KAChB,SAAS,QAAQ,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE;KACrE,cAAc;KAEd,MAAM,OAAO,EAAE,SAAS,QAAQ;KAChC,KAAK,MAAM,OAAO,OAAO,KAAK,WAAW,GACvC,YAAY,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,IAAI;KAG1D,MAAM,aAAa,SAAS;KAC5B,cAAc,MAAM,UAAU,aAAa,KAAK;KAChD,IAAI,YAAY;MACd,MAAM,aAAa,EAAE,SAAS,cAAc,EAAE,iBAAiB;MAC/D,IAAI,YAAY,UAAU,IAAI,WAAW,KAAK,WAAW,GAAG;MAC5D,MAAM,aAAa,EAAE,SAAS,cAAc,EAAE,iBAAiB;MAC/D,IAAI,YAAY,UAAU,IAAI,KAAK,MAAM,WAAW,MAAM,GAAI,GAAG,KAAK,MAAM,WAAW,MAAM,GAAI,CAAC;MAClG,MAAM,WAAW,EAAE,SAAS,YAAY,EAAE,iBAAiB;MAC3D,IAAI,UAAU,QAAQ,IAAI,EAAE,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,CAAC;KAChG;KACA,YAAY,cACV,SAAS,SACL,2BACA,SAAS,cACP,qCACA,YAAY,EAAE,SAAS,aAAa,EAAE,UACnC,KAAK,OAAO,EAAE,SAAS,WAAW,OAAU,GAAI,EAAE;IAE/D;IAEA,MAAM,WAAW,OAAO,UAUH;KACnB,QAAQ,cAAc;KACtB,IAAI;MACF,MAAM,SAAS,MAAM,IAAI,SAAS;OAAE,QAAQ;OAAQ,MAAM,KAAK,UAAU,KAAK;MAAE,CAAC;MACjF,IAAI,QAAQ,IAAI;OACd,UAAU,MAAM;OAChB,QAAQ,cACN,OAAO,OAAO,kBAAkB,cAAc,KAAK,QAC7C,OAAO,qBAAqB,GAAG,OAAO,qBAAqB,UAC3D,OAAO,kBAAkB,OAAO,SAAS,QAAQ,SACtD,OAAO,SAAS,aACb,OAAO,OAAO,QAAQ,WAAW,IAAI,GAAG,OAAO,QAAQ,WAAW,IAAI,UAChE,KAAK,OAAO,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAI,EAAE,GAC5D,KAAK,OAAO,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAI,EAAE,SAC3D,OAAO,QAAQ,WACZ,SAAS,OAAO,QAAQ,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,EAAE,IAAI,OAAO,QAAQ,SAAS,MAAM,IAAA,CAAM,QAAQ,CAAC,EAAE,KAC3G,WACJ,MACA,MACJ,gBAAgB,SAAS,OAAO,OAAO,kBAAkB,CAAC,CAAC,OAC1D,OAAO,cAAc,QAAQ,OAAO,OAAO,WAAW,aAAa;MACxE,OACE,QAAQ,cAAc,QAAQ,QAAQ,SAAS;KAEnD,SAAS,OAAY;MACnB,QAAQ,cAAc,QAAQ,OAAO,WAAW;KAClD;IACF;IAEA,UAAU,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,iBAAiB,UAAU,QAAQ,CAAC,CAAC;IAEhG,SAAS,iBAAiB,eAAe;KACvC,SAAS,cAAc,GAAG,SAAS,MAAM;IAC3C,CAAC;IACD,SAAS,iBAAiB,eAAe;KACvC,SAAS,cAAc,GAAG,SAAS,MAAM;IAC3C,CAAC;IACD,SAAS,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,sBAAsB,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;IACzG,SAAS,iBAAiB,gBAAgB,KAAK,SAAS,EAAE,sBAAsB,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;IAEzG,CAAM,YAAY;KAChB,IAAI;MACF,UAAU,MAAM,IAAI,OAAO,CAAC;KAC9B,QAAQ;MACN,QAAQ,cAAc;KACxB;IACF,EAAA,CAAG;IAEH,IAAI,aAAa;IAEjB,WAAW,iBAAiB,eAAe;KACzC,CAAM,YAAY;MAChB,WAAW,WAAW;MACtB,YAAY,mCAAmC;MAC/C,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OACzE,IAAI,QAAQ,IACV,YAAY,GAAG,OAAO,WAAW,MAAM,KAAK,GAAG,OAAO,WAAW,OAAO,WAAW,OAAO,EAAE;YAE5F,YAAY,KAAK,QAAQ,WAAW,UAAU,KAAK;MAEvD,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,WAAW,WAAW;MACtB,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;IAED,YAAY,iBAAiB,eAAe;KAC1C,CAAM,YAAY;MAChB,YAAY,WAAW;MACvB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,mBAAmB;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OAC1E,IAAI,QAAQ,IACV,YACE,eAAe,OAAO,IAAI,uFAG5B;YAEA,YAAY,QAAQ,QAAQ,WAAW,OAAO,aAAa,QAAQ,OAAO,+BAA+B,KAAK;MAElH,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,YAAY,WAAW;KACzB,EAAA,CAAG;IACL,CAAC;IAED,WAAW,iBAAiB,eAAe;KACzC,CAAM,YAAY;MAChB,WAAW,WAAW;MAGtB,WAAW,QAAQ,OAAO;MAC1B,YACE,0DACF;MACA,IAAI;OAIF,MAAM,SAAS,MAAM,IAAI,kBAAkB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,CAAC;OAAE,CAAC;OACpG,IAAI,QAAQ,SAAS,WAAW;QAE9B,IAAI,OAAO,IACT,YAAY,GAAG,OAAO,WAAW,MAAM,KAAK,GAAG,OAAO,UAAU,OAAO,UAAU,IAAI,OAAO,QAAQ,KAAK,MAAM,OAAO,WAAW,OAAO,EAAE;aAE1I,YAAY,KAAK,OAAO,WAAW,SAAS,OAAO,UAAU,KAAK,MAAM,KAAK;OAEjF,OAAO,IAAI,CAAC,QAAQ,SAClB,YACE,QAAQ,WAAW,iBACf,0EACA,QAAQ,QAAQ,UAAU,UAC9B,KACF;YAEA,YAAY,sBAAsB;MAEtC,SAAS,OAAY;OACnB,YAAY,YAAY,OAAO,WAAW,SAAS,KAAK;MAC1D;MACA,OAAO,WAAW,QAAQ;MAC1B,aAAa,KAAK,IAAI,IAAI;MAC1B,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;IAED,SAAS,iBAAiB,eAAe;KACvC,CAAM,YAAY;MAChB,MAAM,MAAM,WAAW,MAAM,KAAK;MAClC,IAAI,CAAC,KAAK;OACR,YAAY,kBAAkB,KAAK;OACnC;MACF;MACA,MAAM,CAAC,WAAW,GAAG,QAAQ,IAAI,MAAM,IAAI;MAC3C,MAAM,QAAQ,UAAU,KAAK;MAC7B,MAAM,SAAS,KAAK,KAAK,GAAG,CAAC,CAAC,KAAK;MACnC,SAAS,WAAW;MACpB,YAAY,cAAc;MAC1B,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,gBAAgB;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU;SAAE;SAAO;QAAO,CAAC;OAAE,CAAC;OACpG,IAAI,QAAQ,IAAI;QACd,IAAI,OAAO,OACT,YAAY,MAAM,OAAO,SAAS,EAAE;aAEpC,YAAY,KAAK,OAAO,UAAU,MAAM,OAAO,QAAQ,OAAO,GAAG,iBAAiB,IAAI;QAExF,WAAW,QAAQ;OACrB,OACE,YAAY,UAAU,QAAQ,SAAS,UAAU,KAAK;MAE1D,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD;MACA,SAAS,WAAW;MACpB,MAAM,QAAQ,KAAK;KACrB,EAAA,CAAG;IACL,CAAC;;;;;;IAOD,MAAM,cAAc,QAA2B,OAAe,cAAsB,QAA0B;KAC5G,IAAI,QAAQ;KACZ,IAAI;KACJ,MAAM,cAAoB;MACxB,QAAQ;MACR,IAAI,UAAU,KAAA,GAAW,OAAO,aAAa,KAAK;MAClD,OAAO,cAAc;MACrB,OAAO,UAAU,OAAO,OAAO;KACjC;KACA,OAAO,cAAc;KACrB,OAAO,iBAAiB,eAAe;MACrC,IAAI,CAAC,OAAO;OACV,QAAQ;OACR,OAAO,cAAc;OACrB,OAAO,UAAU,IAAI,OAAO;OAC5B,QAAQ,OAAO,WAAW,OAAO,GAAK;OACtC;MACF;MACA,MAAM;MACN,IAAI;KACN,CAAC;IACH;IAEA,MAAM,YAAY,cAA6B;KAC7C,CAAM,YAAY;MAChB,UAAU,WAAW;MACrB,UAAU,WAAW;MACrB,IAAI;OACF,MAAM,IAAI,WAAW;QAAE,QAAQ;QAAQ,MAAM;OAAK,CAAC;OAEnD,WAAW,QAAQ;OACnB,YAAY,YAAY,uBAAuB,0BAA0B;OACzE,MAAM,QAAQ,KAAK;OACnB,IAAI,WAAW;QACb,MAAM,SAAS,MAAM,IAAI,kBAAkB;SAAE,QAAQ;SAAQ,MAAM;QAAK,CAAC;QACzE,IAAI,QAAQ,YAAY,OACtB,YAAY,kBAAkB,QAAQ,UAAU,OAAO,kBAAkB,KAAK;aACzE;SACL,aAAa,KAAK,IAAI,IAAI;SAC1B,YAAY,oCAAoC;QAClD;OACF;MACF,SAAS,OAAY;OACnB,YAAY,QAAQ,OAAO,WAAW,SAAS,KAAK;MACtD,UAAU;OACR,MAAM,QAAQ,KAAK;MACrB;KACF,EAAA,CAAG;IACL;IAEA,WAAW,WAAW,UAAU,eAAe,SAAS,KAAK,CAAC;IAC9D,WAAW,WAAW,aAAa,kBAAkB,SAAS,IAAI,CAAC;IAEnE,QAAQ,iBAAiB,eAAe;KACtC,CAAM,YAAY;MAChB,QAAQ,WAAW;MACnB,QAAQ,MAAM,UAAU;MACxB,QAAQ,YAAY;MACpB,QAAQ,cAAc;MACtB,IAAI;OACF,MAAM,SAAS,MAAM,IAAI,SAAS;QAAE,QAAQ;QAAQ,MAAM,KAAK,UAAU,EAAE,OAAO,YAAY,MAAM,CAAC;OAAE,CAAC;OACxG,IAAI,QAAQ,IAAI;QACd,QAAQ,YAAY;QACpB,QAAQ,cAAc,KAAK,OAAO,GAAG,OAAO,OAAO,MAAM,IAAI,OAAO,QAAQ,UAAU,OAAO,YAAY,YAAY,OAAO,cAAc;OAC5I,OAAO;QACL,QAAQ,YAAY;QACpB,QAAQ,cAAc,KAAK,QAAQ,OAAO,IAAI,OAAO,KAAK,MAAM,KAAK,QAAQ,SAAS;OACxF;MACF,SAAS,OAAY;OACnB,QAAQ,YAAY;OACpB,QAAQ,cAAc,KAAK,OAAO,WAAW;MAC/C;MACA,QAAQ,WAAW;KACrB,EAAA,CAAG;IACL,CAAC;IAED,WAAW,iBAAiB,eAAe,KAAK,QAAQ,KAAK,CAAC;IAE9D,KAAK,OAAO,IAAI;IAChB,QAAa,KAAK;IAElB,MAAM,iBAAiB,OAAO,YAAY,YAAY,GAAM;IAE5D,QAAQ,OAAO,kBAAkB;KAC/B,MAAM,SAAS,cAAc,KAAK,IAAI,IAAI;KAI1C,IAAI,mBAAmB,KAAK,IAAI,GAAG,kBAAkB,MAAM,GAAG,aAAkB;KAChF,IAAI,QAAQ;MACV,QAAa,IAAI;MACjB;KACF;KAEA,IAAI,KAAK,OAAO,IAAI,KAAM,QAAa,KAAK;IAC9C,GAAG,GAAI;IAEP,aAAa;KACX,WAAW;KACX,IAAI,UAAU,KAAA,GAAW,OAAO,cAAc,KAAK;KACnD,OAAO,cAAc,cAAc;IACrC;GACF,GAAG,CAAC,CAAC;GAEL,QAAA,GAAOC,MAAAA,cAAAA,CAAc,OAAO,EAAE,KAAK,QAAQ,CAAC;EAC9C;EAEA,SAAgB,MAAM,KAA0B;GAC9C,IAAI,aAEA,IAAI,MAAM,OAAO,0BACf,IAAI,MAAM,SAAS;IAAE,MAAM;IAAoB,IAAI;IAAsB,OAAO;IAAI,aAAa;GAAgB,UAAA,GAC/GA,MAAAA,cAAAA,CAAc,KAAK,CACrB,CACF,GACF,mCACF;EACF"}