Files
auto_agent/src/web/index.html
T

595 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&family=Ma+Shan+Zheng&family=Long+Cang&family=ZCOOL+QingKe+HuangYou&display=swap"
rel="stylesheet"
/>
<link rel="icon" href="/image/favicon.svg" sizes="any" />
<link rel="stylesheet" href="/app.css?v=6" />
</head>
<body>
<div class="bg-scene" id="bgScene" aria-hidden="true">
<div class="bg-image" id="bgImageLayer"></div>
<div class="bg-vignette" aria-hidden="true"></div>
</div>
<div class="app-root" id="app">
<!-- 自定义标题栏(仅 Electron 无边框模式) -->
<header class="title-bar" id="titleBar">
<span class="title-bar-name">minor Agent</span>
<div class="title-bar-actions">
<button class="title-bar-btn title-bar-btn--settings" id="tbSettings" title="系统配置" aria-label="打开系统配置">&#x2699;</button>
<button class="title-bar-btn" id="tbMin" title="最小化" aria-label="最小化">&#xE921;</button>
<button class="title-bar-btn" id="tbMax" title="最大化" aria-label="最大化">&#xE922;</button>
<button class="title-bar-btn title-bar-btn--close" id="tbClose" title="关闭" aria-label="关闭">&#xE8BB;</button>
</div>
</header>
<div class="app-body">
<div class="drawer-backdrop" id="drawerBackdrop" aria-hidden="true"></div>
<aside class="expand-rail" id="expandRail" aria-hidden="true">
<button type="button" class="rail-btn" id="expandBtn" title="展开侧栏">⟩</button>
</aside>
<aside class="sidebar" id="sidebar">
<div class="sidebar-resize-handle" id="sidebarResizeHandle" title="拖动调整侧栏宽度"></div>
<div class="sidebar-head">
<div class="mode-switch" id="modeSwitch">
<div class="mode-switch-inner">
<span class="mode-switch-item" data-mode="edit" id="modeEditBtn">Edit</span>
<span class="mode-switch-sep">|</span>
<span class="mode-switch-item" data-mode="cron" id="modeCronBtn">Cron</span>
<span class="mode-switch-sep">|</span>
<span class="mode-switch-item mode-switch-item--chat active" data-mode="chat" id="modeChatBtn">
Chat
<span class="mode-switch-expand" id="modeChatExpand">
<img src="/image/新增会话.svg" alt="新增会话" width="20" height="20" class="new-chat-icon" id="newSessionBtn" title="新建会话" />
</span>
</span>
</div>
</div>
</div>
<div class="sidebar-list-wrap" id="sessionListWrap">
<ul class="session-list" id="sessionList" role="listbox" aria-label="会话列表"></ul>
</div>
<!-- 文档树区域(Edit 模式) -->
<div class="doc-tree-wrap mode-hidden" id="docTreeWrap">
<div class="doc-tree" id="docTree"></div>
</div>
<!-- 定时任务列表(Cron 模式) -->
<div class="cron-list-wrap mode-hidden" id="cronListWrap">
<div class="cron-list-head">
<span class="cron-list-title">定时任务</span>
<button type="button" class="cron-new-btn" id="cronNewBtn" title="新建定时任务">+</button>
</div>
<ul class="cron-list" id="cronList" role="listbox" aria-label="定时任务列表"></ul>
</div>
<p class="sidebar-hint">鼠标悬停可重命名或删除会话。</p>
<div class="sidebar-collapse">
<button type="button" class="collapse-fab" id="collapseBtn" title="收起侧栏">⟨</button>
</div>
</aside>
<main class="main" id="mainPanel">
<header class="main-header">
<button
type="button"
class="menu-btn"
id="menuBtn"
aria-expanded="false"
aria-controls="sidebar"
title="会话与设置"
>
☰
</button>
<div class="main-header-text" id="mainHeaderText">
<h1 class="title">多模态Agent</h1>
</div>
<button
type="button"
class="settings-btn"
id="settingsBtn"
title="系统配置"
aria-label="打开系统配置"
>
⚙
</button>
</header>
<div class="main-content-wrap" id="mainContentWrap">
<!-- Chat 模式 -->
<section class="chat-shell" id="chatPanel">
<div class="chat-placeholder" id="chatPlaceholder">
<h3></h3>
<p></p>
<p class="muted"></p>
</div>
<div class="chat-messages" id="chatMessages" hidden></div>
</section>
<!-- 文件预览浮窗 -->
<div class="file-preview-overlay" id="filePreviewOverlay" hidden>
<div class="file-preview-dialog" id="filePreviewDialog">
<div class="file-preview-header">
<span class="file-preview-name" id="filePreviewName"></span>
<span class="file-preview-status" id="filePreviewStatus"></span>
<div class="file-preview-actions">
<button type="button" class="file-preview-btn" id="filePreviewDownload" title="下载">&#x2B07;</button>
<button type="button" class="file-preview-btn file-preview-close" id="filePreviewClose" title="关闭">&#x2715;</button>
</div>
</div>
<div class="file-preview-body" id="filePreviewBody"></div>
</div>
</div>
<!-- 文档修改面板 -->
<div class="doc-mod-panel" id="docModPanel" hidden>
<div class="doc-mod-header">
<span>文档变更</span>
<button type="button" class="doc-mod-close" id="docModClose">&#x2715;</button>
</div>
<div class="doc-mod-list" id="docModList"></div>
<div class="doc-mod-actions" id="docModActions" hidden>
<button type="button" class="btn btn-primary" id="docModAcceptAll">全部保留</button>
<button type="button" class="btn btn-danger" id="docModRejectAll">全部撤销</button>
</div>
</div>
</div>
<!-- 欢迎页(空会话居中时显示,在 main-content-wrap 外防止被折叠) -->
<div class="welcome-view" id="welcomeView" hidden>
<div class="welcome-content">
<div class="welcome-left">
<img src="/image/icon.svg" alt="" class="welcome-icon" />
</div>
<div class="welcome-right">
<h2 class="welcome-greeting" id="welcomeGreeting">欢迎使用 minor Agent</h2>
<div class="recent-folders-wrap" id="recentFoldersWrap" hidden>
<div class="recent-folders-label">最近打开</div>
<ul class="recent-folders-list" id="recentFoldersList"></ul>
</div>
</div>
<div class="welcome-clock-wrap">
<span class="welcome-clock" id="welcomeClock"></span>
</div>
</div>
</div>
<footer class="composer">
<div class="composer-refs" id="composerRefs" hidden></div>
<div class="composer-row">
<div class="mm-input" role="group" aria-label="多模态输入">
<div class="composer-input-shell">
<div class="input-inline-area" id="inputInlineArea">
<div class="input-inline-images" id="inputInlineImages"></div>
<div class="input-inline-chips" id="inputInlineChips"></div>
</div>
<textarea
id="textInput"
rows="2"
placeholder="输入消息或添加附件,Enter 发送(Shift+Enter 换行)"
></textarea>
<div class="composer-input-bottom-row">
<div class="composer-input-bottom-left">
<button type="button" class="voice-toggle-btn" id="voiceToggleBtn" title="文字/语音回复开关" aria-label="语音回复开关" aria-pressed="false">
<img src="/image/文本语音 .svg" alt="语音" aria-hidden="true" />
</button>
<div class="inline-drop-wrap" id="accessModeWrap">
<button type="button" class="inline-drop-trigger inline-drop-trigger--icon" id="accessModeTrigger" aria-label="工作空间访问模式" title="工作空间访问模式:限制访问 / 权限审查 / 完全访问">
<img src="/image/限制访问.svg" alt="访问模式" id="accessModeIcon" />
</button>
<div class="inline-drop-panel" id="accessModePanel" hidden>
<div class="inline-drop-item is-selected access-mode-item--restricted" data-value="restricted">限制访问</div>
<div class="inline-drop-item access-mode-item--approval" data-value="approval">权限审查</div>
<div class="inline-drop-item access-mode-item--full" data-value="full">完全访问</div>
</div>
</div>
<span class="input-actions-sep"></span>
<button type="button" class="action-icon-btn" id="actionTodoBtn" title="Todo List">
<img src="/image/todolist.svg" alt="Todo" />
</button>
<button type="button" class="action-icon-btn" id="actionPendingBtn" title="人机交互">
<img src="/image/人机交互.svg" alt="交互" />
</button>
<button type="button" class="action-icon-btn" id="actionDocBtn" title="文档变更">
<img src="/image/文档按钮.svg" alt="文档" />
</button>
<button type="button" class="action-icon-btn" id="actionCanvasBtn" title="画板">
<img src="/image/画板.svg" alt="画板" />
</button>
</div>
<div class="composer-input-bottom-right">
<div class="token-ring-wrap" id="tokenRingWrap">
<button type="button" class="token-ring-btn" id="tokenRingBtn" title="上下文 Token 占用" aria-label="上下文 Token 占用">
<svg class="token-ring-svg" id="tokenRingSvg" viewBox="0 0 30 30" width="13" height="13">
<circle class="token-ring-bg" cx="15" cy="15" r="11"></circle>
<circle class="token-ring-fill" id="tokenRingFill" cx="15" cy="15" r="11"></circle>
</svg>
</button>
<div class="token-ring-panel" id="tokenRingPanel" hidden>
<div class="token-ring-capacity" id="tokenRingCapacity">上下文容量 0k / 0k (0%)</div>
<div class="token-ring-rate-track" id="tokenRingRateTrack">
<div class="token-ring-rate-fill" id="tokenRingRateFill"></div>
<div class="token-ring-threshold" id="tokenRingThreshold"></div>
</div>
<div class="token-ring-rate-label" id="tokenRingRateLabel">压缩阈值 60%(在环境变量中修改)</div>
<div class="token-ring-breakdown" id="tokenRingBreakdown"></div>
</div>
</div>
<div class="inline-drop-wrap" id="modelSelectWrap">
<button type="button" class="inline-drop-trigger inline-drop-trigger--model" id="modelTrigger" aria-label="模型选择">模型</button>
<div class="inline-drop-panel" id="modelPanel" hidden></div>
</div>
<div class="inline-drop-wrap" id="thinkModeWrap">
<button type="button" class="inline-drop-trigger inline-drop-trigger--icon" id="thinkModeTrigger" aria-label="思考模式" title="思考模式:low / high / xhigh / max / ultra">
<img src="/image/思考模式.svg" alt="思考" id="thinkModeIcon" />
</button>
<div class="inline-drop-panel think-mode-panel" id="thinkModePanel" hidden>
<div class="think-mode-labels" id="thinkModeLabels"></div>
<div class="think-mode-track" id="thinkModeTrack">
<div class="think-mode-fill" id="thinkModeFill"></div>
<div class="think-mode-handle" id="thinkModeHandle"></div>
</div>
</div>
</div>
<div class="ws-btn-wrap" id="wsBtnWrap">
<button type="button" class="ws-btn" id="wsBtn" title="选择工作空间" aria-label="选择工作空间">
<img src="/image/文件夹.svg" alt="" aria-hidden="true" />
</button>
<div class="ws-dropdown" id="wsDropdown" hidden>
<div class="ws-dropdown-header">工作空间</div>
<div class="ws-dropdown-list" id="wsDropdownList"></div>
<div class="ws-dropdown-add">
<button type="button" class="ws-add-btn" id="wsAddBtn" title="添加工作空间">+ 添加工作空间</button>
</div>
</div>
</div>
<button type="button" class="mic-btn" id="micBtn" title="使用麦克风录音" aria-pressed="false" aria-label="使用麦克风录音">
<img src="/image/microphone.svg" alt="" aria-hidden="true" />
</button>
<label class="attachment-btn" title="上传附件" aria-label="上传附件">
<img src="/image/附件.svg" alt="" aria-hidden="true" class="attachment-btn-icon" />
<input type="file" id="fileInput" multiple hidden />
</label>
<button type="button" class="send-btn" id="sendBtn" title="发送" aria-label="发送">
<img src="/image/发送.svg" alt="" aria-hidden="true" class="send-btn-icon" />
</button>
</div>
</div>
</div>
</div>
</div>
</footer>
</main>
</div>
<!-- 新建会话确认弹窗 -->
<div class="confirm-overlay" id="newSessionConfirm" hidden>
<div class="confirm-dialog">
<div class="confirm-title">新建会话</div>
<div class="confirm-body">
当前工作区存在 <strong id="confirmPendingCount">0</strong> 个未处理的文件变更。
请先接受或撤销这些变更后再新建会话。
</div>
<div class="confirm-actions">
<button type="button" class="confirm-btn confirm-btn--secondary" id="confirmCancel">取消</button>
<button type="button" class="confirm-btn confirm-btn--danger" id="confirmRejectAll">全部撤销并新建</button>
<button type="button" class="confirm-btn confirm-btn--primary" id="confirmAcceptAll">全部接受并新建</button>
</div>
</div>
</div>
<!-- 定时任务编辑/新建弹窗 -->
<div class="cron-edit-overlay" id="cronEditOverlay" hidden>
<div class="cron-edit-dialog" role="dialog" aria-modal="true" aria-label="定时任务编辑">
<div class="cron-edit-header">
<h3 id="cronEditTitle">新建定时任务</h3>
<button type="button" class="cron-edit-close" id="cronEditClose" aria-label="关闭">✕</button>
</div>
<div class="cron-edit-body">
<div class="cron-field">
<label class="cron-field-label" for="cronEditName">任务名称</label>
<input type="text" id="cronEditName" placeholder="如:每日晨报" autocomplete="off" />
</div>
<div class="cron-field">
<label class="cron-field-label" for="cronEditPrompt">任务提示词</label>
<textarea id="cronEditPrompt" placeholder="触发时投递给 Agent 的指令,如:查询今日天气并生成晨报"></textarea>
<span class="cron-field-hint">该提示词将在每次触发时投递给 Agent 自主执行。</span>
<span class="cron-field-hint cron-field-hint--warn" id="cronAccessModeHint" hidden>⚠️ 定时任务无人值守执行:当前访问模式为「限制访问/权限审查」时,超出工作空间的操作会被拦截或退化拦截,建议先切换为「完全访问」。</span>
</div>
<div class="cron-field">
<span class="cron-field-label">触发方式</span>
<div class="cron-trigger-types">
<button type="button" class="cron-trigger-type-btn" data-trigger-type="cron">定时闹钟</button>
<button type="button" class="cron-trigger-type-btn" data-trigger-type="interval">固定间隔</button>
<button type="button" class="cron-trigger-type-btn" data-trigger-type="once">一次性延时</button>
</div>
<!-- 闹钟式:周几方块 + 时分滚轮选择器 -->
<div data-trigger-field="cron" hidden>
<div class="cron-alarm-picker" id="cronAlarmPicker" data-mode="alarm">
<div class="cron-weekday-row" id="cronWeekdayRow" role="group" aria-label="选择星期"></div>
<div class="cron-time-row">
<span class="cron-time-label">触发时间</span>
<div class="cron-time-picker" id="cronTimePicker" role="group" aria-label="选择时间"></div>
</div>
<div class="cron-advanced-toggle">
<button type="button" id="cronAdvancedToggle">高级:手动输入 cron 表达式</button>
</div>
<div class="cron-advanced-raw" id="cronAdvancedRaw" hidden>
<input type="text" id="cronEditCron" placeholder="0 9 * * *(分 时 日 月 周)" autocomplete="off" />
<span class="cron-field-hint">标准 5 段 cron,如 <code>0 9 * * *</code> 每天9点、<code>*/30 * * * *</code> 每30分钟。</span>
</div>
</div>
</div>
<div data-trigger-field="interval" hidden>
<input type="number" id="cronEditInterval" placeholder="3600(秒)" min="1" />
<span class="cron-field-hint">固定间隔秒数,如 3600 = 每小时。</span>
</div>
<!-- 一次性延时:主题化日期 + 时分选择器(替代原生 datetime-local) -->
<div data-trigger-field="once" hidden>
<div class="cron-once-picker" id="cronOncePicker">
<div class="cron-once-date-row">
<span class="cron-time-label">日期</span>
<div class="cron-date-picker" id="cronDatePicker"></div>
</div>
<div class="cron-time-row">
<span class="cron-time-label">时间</span>
<div class="cron-time-picker" id="cronOnceTimePicker" role="group" aria-label="选择时间"></div>
</div>
<input type="hidden" id="cronEditRunAt" />
</div>
</div>
</div>
<div class="cron-field">
<label class="cron-field-label" for="cronEditTimeout">单次执行超时(秒)</label>
<input type="number" id="cronEditTimeout" placeholder="300" min="10" value="300" />
</div>
<div class="cron-toggle-row">
<span class="cron-field-label">启用任务</span>
<button type="button" class="cron-switch" id="cronEnabledSwitch" data-on="true" aria-label="启用任务"></button>
</div>
</div>
<div class="cron-edit-footer">
<button type="button" class="cron-edit-btn cron-edit-btn--cancel" id="cronEditCancel">取消</button>
<button type="button" class="cron-edit-btn cron-edit-btn--save" id="cronEditSave">保存</button>
</div>
</div>
</div>
</div>
<!-- 系统配置模态框 -->
<div class="settings-overlay" id="settingsOverlay" hidden>
<div class="settings-dialog" id="settingsDialog" role="dialog" aria-modal="true" aria-label="系统配置">
<div class="settings-header">
<h2>系统配置</h2>
<button type="button" class="settings-close-btn" id="settingsCloseBtn" aria-label="关闭">✕</button>
</div>
<div class="settings-tabs">
<button type="button" class="settings-tab active" data-tab="agent">Agent</button>
<button type="button" class="settings-tab" data-tab="tool">工具</button>
<button type="button" class="settings-tab" data-tab="env">环境变量</button>
<button type="button" class="settings-tab" data-tab="skills">Skills</button>
<button type="button" class="settings-tab" data-tab="other">GUI设置</button>
<button type="button" class="settings-tab" data-tab="theme">主题/语言</button>
</div>
<div class="settings-body">
<!-- Agent 配置面板 -->
<div class="settings-panel active" data-panel="agent" id="agentPanel">
<div class="settings-toolbar">
<button type="button" class="btn btn-primary" id="addAgentBtn">+ 添加 Agent</button>
</div>
<div class="settings-agents-list" id="agentsList"></div>
<button type="button" class="btn btn-primary settings-save-btn" data-save="agents">保存 Agent 配置</button>
</div>
<!-- 工具配置面板 -->
<div class="settings-panel" data-panel="tool" id="toolPanel" hidden>
<div class="settings-tools-list" id="toolsList"></div>
<button type="button" class="btn btn-primary settings-save-btn" data-save="tools">保存工具配置</button>
</div>
<!-- 环境变量配置面板 -->
<div class="settings-panel" data-panel="env" id="envPanel" hidden>
<div class="settings-env-list" id="envList"></div>
<button type="button" class="btn btn-primary settings-save-btn" data-save="env">保存环境变量配置</button>
</div>
<!-- Skills 管理面板 -->
<div class="settings-panel" data-panel="skills" id="skillsPanel" hidden>
<div class="settings-toolbar">
<div class="settings-toolbar-buttons">
<button type="button" class="btn btn-primary" id="addSkillBtn">+ 创建 Skill</button>
<span class="skill-import-wrap">
<button type="button" class="btn btn-primary import-skill-btn" id="importSkillBtn" title="导入 zip 或文件夹(自动解析 skill.md)">导入 Skill</button>
</span>
</div>
</div>
<input type="file" id="importSkillZipInput" accept=".zip" hidden />
<input type="file" id="importSkillFolderInput" webkitdirectory multiple hidden />
<div class="skills-list" id="skillsList"></div>
<!-- Skill 编辑/创建弹窗 -->
<div class="skill-editor-overlay" id="skillEditorOverlay" hidden>
<div class="skill-editor-dialog" role="dialog" aria-modal="true" aria-label="编辑 Skill">
<div class="skill-editor-header">
<h3 id="skillEditorTitle">创建 Skill</h3>
<button type="button" class="skill-editor-close" id="skillEditorClose">✕</button>
</div>
<div class="skill-editor-body">
<div class="setting-field">
<label>名称 (name)</label>
<input type="text" id="skillNameInput" placeholder="skill 名称(即文件夹名,英文+下划线)" />
</div>
<div class="setting-field">
<label>描述 (description)</label>
<input type="text" id="skillDescInput" placeholder="简要描述此 skill 的用途" />
</div>
<div class="setting-field">
<label>标签 (tags,逗号分隔,可选)</label>
<input type="text" id="skillTagsInput" placeholder="e.g. python, 自动化, GUI" />
</div>
<div class="setting-field">
<div class="toggle-row">
<label class="toggle-switch">
<input type="checkbox" id="skillEnableCheck" checked />
<span class="toggle-slider"></span>
</label>
<span class="toggle-label" id="skillEnableLabel">启用此 Skill(关闭后 Agent 将无法看到此 Skill)</span>
</div>
</div>
<div class="setting-field">
<label>指南内容 (skill.md)</label>
<textarea id="skillContentInput" rows="14" placeholder="编写此 skill 的操作指南(Markdown 格式)..."></textarea>
</div>
<div class="setting-field">
<label>附件文件</label>
<div class="skill-attachments" id="skillAttachmentsList"></div>
<label class="btn btn-ghost" style="margin-top:0.4rem;display:inline-flex;align-items:center;gap:0.3rem;">
+ 上传附件
<input type="file" id="skillFileInput" multiple hidden />
</label>
</div>
</div>
<div class="skill-editor-footer">
<span class="skill-editor-status" id="skillEditorStatus"></span>
<button type="button" class="btn btn-secondary" id="skillEditorCancel">取消</button>
<button type="button" class="btn btn-primary" id="skillEditorSave">保存</button>
</div>
</div>
</div>
</div>
<!-- 其他设置面板 -->
<div class="settings-panel" data-panel="other" id="otherPanel" hidden>
<div class="settings-section">
<h4 class="settings-section-title">GUI 操作区域</h4>
<p class="settings-hint">选择 Agent 进行 GUI 自动化操作(点击、截图、拖拽等)的目标显示器。</p>
<div class="gui-monitors-grid" id="guiMonitorsGrid"></div>
</div>
<button type="button" class="btn btn-primary settings-save-btn" data-save="gui">保存 GUI 设置</button>
</div>
<!-- 主题/语言面板 -->
<div class="settings-panel" data-panel="theme" id="themePanel" hidden>
<div class="settings-section">
<h4 class="settings-section-title">主题预设</h4>
<div class="theme-grid" id="themeGrid"></div>
</div>
<div class="settings-section">
<h4 class="settings-section-title" style="margin-bottom:0.3rem;">自定义背景</h4>
<p class="settings-hint">输入本地图片路径或 URL(支持 jpg/png/webp),留空恢复默认。</p>
<div class="setting-field" style="margin-bottom:0.4rem;">
<div class="bg-input-row">
<input type="text" id="bgImageInput" placeholder="例如:C:\wallpaper.jpg 或 https://..." />
<button type="button" class="bg-picker-btn" id="bgImagePickerBtn" title="选择图片">⋮</button>
<input type="file" id="bgImageFileInput" accept="image/*" hidden />
</div>
</div>
<div style="display:flex;gap:0.5rem;">
<button type="button" class="btn btn-primary" id="bgImageApplyBtn">应用</button>
<button type="button" class="btn btn-secondary" id="bgImageResetBtn">恢复默认</button>
</div>
<span class="bg-preview-status" id="bgPreviewStatus" style="font-size:11px;color:#6a6a6a;display:block;margin-top:0.4rem;"></span>
</div>
<div class="settings-section">
<h4 class="settings-section-title" style="margin-bottom:0.3rem;">背景效果</h4>
<div style="display:flex;gap:1rem;">
<div class="setting-field" style="flex:1;">
<label style="font-size:0.7rem;display:block;margin-bottom:0.15rem;">模糊度 <span id="bgBlurVal" style="color:var(--accent);">0px</span></label>
<input type="range" id="bgBlurSlider" min="0" max="10" step="1" value="0" class="compact-slider">
</div>
<div class="setting-field" style="flex:1;">
<label style="font-size:0.7rem;display:block;margin-bottom:0.15rem;">暗角 <span id="bgVignetteVal" style="color:var(--accent);">0%</span></label>
<input type="range" id="bgVignetteSlider" min="0" max="1" step="0.05" value="0" class="compact-slider">
</div>
</div>
</div>
<div class="settings-section">
<h4 class="settings-section-title" style="margin-bottom:0.3rem;">字体与字号</h4>
<div style="display:flex;gap:0.5rem;align-items:flex-end;">
<div style="flex:1;">
<label style="font-size:0.7rem;display:block;margin-bottom:0.2rem;">字体</label>
<select id="fontFamilySelect" class="lang-select" style="max-width:none;">
<option value="system">系统默认</option>
<option value="sans">无衬线</option>
<option value="serif">衬线</option>
<option value="mono">等宽</option>
<option value="zcool">站酷快乐体</option>
<option value="mashan">马山正楷</option>
<option value="longcang">龙藏手写</option>
<option value="zcool-qy">站酷庆科黄油体</option>
</select>
</div>
<div style="flex:1;">
<label style="font-size:0.7rem;display:block;margin-bottom:0.2rem;">字号</label>
<select id="fontSizeSelect" class="lang-select" style="max-width:none;">
<option value="12px">12px</option>
<option value="13px">小 (13px)</option>
<option value="14px" selected>中 (14px)</option>
<option value="15px">大 (15px)</option>
<option value="16px">16px</option>
<option value="18px">特大 (18px)</option>
<option value="20px">20px</option>
</select>
</div>
</div>
</div>
<div class="settings-section" style="margin-top:1.2rem;">
<h4 class="settings-section-title">界面语言</h4>
<div class="lang-select-wrap">
<select id="langSelect" class="lang-select"></select>
</div>
<p class="theme-note">主题即时生效,语言需刷新页面后生效。</p>
</div>
</div>
</div>
<div class="settings-footer">
<span class="settings-status" id="settingsStatus"></span>
<button type="button" class="btn btn-secondary" id="settingsCancelBtn">取消</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"></script>
<!-- Monaco Editor: CDN 优先(Web端),本地 npm 包兜底(Electron 离线) -->
<script>
(function() {
var base = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs';
var s = document.createElement('script');
s.src = base + '/loader.js';
s.onload = function() {
require.config({ paths: { vs: base } });
window._monacoReady = new Promise(function(r) { require(['vs/editor/editor.main'], function() { r(monaco); }); });
};
s.onerror = function() {
var localBase = '/packages/node_modules/monaco-editor/min/vs';
var ls = document.createElement('script');
ls.src = localBase + '/loader.js';
ls.onload = function() {
require.config({ paths: { vs: localBase } });
window._monacoReady = new Promise(function(r) { require(['vs/editor/editor.main'], function() { r(monaco); }); });
};
document.head.appendChild(ls);
};
document.head.appendChild(s);
})();
</script>
<script>
// 诊断:验证基本 JS 可执行
window.__jsDiag = { ok: true, ts: Date.now() };
// 全局错误捕获(模块加载失败也会出现在这里)
function _showJsError(msg) {
var ph = document.getElementById('chatPlaceholder');
if (ph) {
ph.hidden = false;
ph.style.display = '';
ph.innerHTML = '<h3>\u26A0 JS \u52A0\u8F7D\u5931\u8D25</h3><p style="color:#ef4444">' +
(msg || 'unknown') +
'</p><p class="muted">\u8BF7\u6253\u5F00\u63A7\u5236\u53F0 (Ctrl+Shift+I) \u67E5\u770B\u8BE6\u60C5</p>';
}
}
window.addEventListener('error', function(e) {
if (e.target && e.target !== window && e.target.tagName === 'SCRIPT') {
_showJsError('Module load: ' + (e.target.src || e.target.dataset?.src || 'app.js'));
} else if (e.message) {
_showJsError(e.message);
}
});
window.addEventListener('unhandledrejection', function(e) {
_showJsError('Promise: ' + (e.reason?.message || String(e.reason)));
});
</script>
<script type="module" src="/app.js?v=6"></script>
</body>
</html>