diff --git a/docs/05-代码审查/UX走查-Web规范合规-2026-10-05.md b/docs/05-代码审查/UX走查-Web规范合规-2026-10-05.md new file mode 100644 index 0000000..f5633bb --- /dev/null +++ b/docs/05-代码审查/UX走查-Web规范合规-2026-10-05.md @@ -0,0 +1,56 @@ +# UX 走查 — Web Interface Guidelines 合规(2026-10-05) + +> 范围:src 下 43 个 .vue 文件(以工作区未提交改动为主,机械反模式全量扫描含存量)。 +> 方法:三路子代理分组走查(A 审批/工具卡域 6 文件、B aichat 域 9 文件、C 项目/知识/审计域 11 文件)+ 主会话 grep 全量机械扫描(transition:all / outline:none / div@click / overscroll / aria 语义)。子代理发现经行号复核;A 组子代理两轮超限,由主会话直读补核。 + +## 一、总体结论 + +**质量中上,无阻断项。** 本批新增代码 a11y 意识明显:svg 批量补 aria-hidden、复制/编辑按钮带 aria-label、MessageList 有 role=log+aria-live、hover 反馈普遍到位、i18n 完整。缺口高度集中在 **4 类系统性模式**,其中 2 类可用全局样式一刀切收敛大半。 + +## 二、系统性缺口(grep 全量实证) + +### 1. 弹层缺 overscroll-behavior: contain —— 全仓 0 命中【覆盖面最广】 +43 文件所有 modal/overlay/浮层(`modal-overlay`×6、`ai-shortcuts-mask`、`ai-agent-peek-mask`、`dep-graph__modal-overlay`、`confirm-mask`、审批浮层等约 12+ 处)无一声明 overscroll-behavior。弹层内容滚到边界后滚轮穿透到底层内容。 +**修复**:全局样式一刀切(`[class*="overlay"], [class*="mask"] { overscroll-behavior: contain; }`)或逐弹层补。P2,一处 CSS。 + +### 2. transition: all —— 33 处 / 22 文件【密度最高】 +行号清单:ApprovalPopup×4(594/632/728/809)、ApprovalOverlay×3(128/200/211)、ChatInput×2(1222/1236)、FilePreview×2、GitChanges×2、Knowledge.vue×2(501/546)、ProjectDetail×2、Tasks×2、App.vue:340(toast)、ConfirmDialog:103、Paginator:146、ToolCard:494、ToolResultBody:542、EmptyState×1、TopBar×1、IdeaDetail×1、AppSidebar:276、FileExplorer×1、ProjectCard×1、AuditLog:659、Ideas:588、Projects×1。 +风险:布局属性被动画(掉帧)、主题切换时全属性过渡发糊。 +**修复**:机械替换为具体属性(color/background-color/border-color/transform/box-shadow)。P3 批量低风险。 + +### 3. outline: none 无 :focus-visible 补偿 —— 19 处 / 14 文件【键盘可达性】 +行号清单:IdeaDetail×3(1096/1112/1153)、ConversationSidebar×2(702/829)、Knowledge.vue×2、Tasks×2、AiChat:972、ChatInput:1197、TaskBoundaryCard:454、TopBar:718、KnowledgeDetail:453、GitChanges×1、SettingsNav×1、TaskBatchBar×1、WorkflowDagDisplay×1、AuditLog:471。 +**修复**:全局兜底 `:focus-visible { outline: 2px solid var(--df-accent); outline-offset: 2px; }`,input 类可保留 border-color 补偿不强制 outline。P2。 + +### 4. div/span 充当可点击元素 —— @click 共 56 处,去 @click.self mask 合规项后约 20 处真缺口【键盘不可达】 +典型(高频优先):ToolCard.vue:11 工具卡折叠头、MessageList.vue:1423 轨迹折叠头、ConflictResolver.vue:7 冲突行、ConversationSidebar.vue:205 归档组折叠头、TopBar.vue:176/192 历史/摘要行、AuditLog.vue:67 审计展开行 + :119 会话行、Dashboard.vue:38/42/46 统计卡、FileExplorer.vue:29/37/42 下拉项+面包屑、FilePreview.vue:124 大纲头、GitChanges.vue:120 加载更多、ProjectCard.vue:2 整卡。 +(`@click.self` 的 mask 点击关闭是合理模式,不在修列。) +**修复**:高频 3 处改 button+样式重置;其余补 tabindex=0 + @keydown.enter/space + role。P2 分批。 + +### 5. icon-only 按钮缺 aria-label(子代理域审 + 抽查) +TopBar 多数按钮有 title(弱补偿);EnrichmentPanel 关闭钮仅 title;ProjectDetail:185/189 编辑/删除 icon 钮仅 title。title 与 aria-label 非等价(部分读屏不读 title)。 +**修复**:aria-label 与 title 同 i18n 键复用,机械补齐。P3。 + +## 三、亮点(已做对的) + +- MessageList.vue:1391 `role="log" + aria-live="polite"`:流式消息读屏可感知(UX 第五轮 P2-G1) +- 本批 git diff 新改 svg 批量补 `aria-hidden="true"`(审批拒绝按钮等实证) +- MessageItem 复制/编辑按钮带 aria-label,hover-only 操作区有 `:focus-within` 显形补偿 +- 消息区键盘链(Esc/IME/滚动锚定)质量好;AuditLog 模板全 button + Paginator 组件分页规范 + +## 四、修复分期建议 + +| 期 | 内容 | 量级 | +|---|---|---| +| P2-1 | 全局两刀::focus-visible 兜底 + 弹层 overscroll-contain | 全局 CSS ~2 处,收敛 2 类大半缺口 | +| P2-2 | 高频 div→button:ToolCard 卡头 / MessageList 轨迹头 / AuditLog 行 | 3 处 | +| P3-1 | transition:all 33 处机械替换具体属性 | sed 级批量 | +| P3-2 | icon-only aria-label 补齐 + 剩余 div 键盘化 | ~15 处 | + +**拍板点**:修复范围「存量 vs 仅本批改动」——机械扫描含存量(transition:all/outline:none 大半为存量代码),若仅清本批改动则这两类大半豁免,工作量降 60%+。 + +## 附:三路子代理域审摘要 + +- **B 组(aichat 域 9 文件)**:22 处。集中 icon-only 缺 aria-label(~12)与 outline 无补偿;4 处 transition:all;3 处 div 点击不可键盘达。逻辑层键盘处理(Esc/IME/双确认)质量好。MessageItem pass。已知线索复核:TopBar model select outline 无补偿属实(:718);EnrichmentPanel 关闭钮仅 title 属实。 +- **C 组(项目/知识/审计域 11 文件)**:弹层 overscroll 系统性缺失(本组 4 文件)+ icon-only ~6 + div 可点击 ~5 + AppSidebar:276 transition:all、KnowledgeDetail:453 outline 裸删。无 v-for 超百项无虚拟化、无 aria-live 缺失。AuditLog/Knowledge.vue cat-chip 规范。 +- **A 组(审批/工具卡域 6 文件,主会话补核)**:ToolCard.vue:11 卡头 div 可点击 + :494 transition:all(diff 中另一处 svg aria-hidden 为本批已修好的实证);ApprovalPopup transition:all×4;ApprovalOverlay×3(128/200/211);ToolResultBody:542;AiChat.vue:972 outline:none,:139 mask 为 @click.self 合规。 diff --git a/docs/07-项目管理/todo归档/2026-10-05.md b/docs/07-项目管理/todo归档/2026-10-05.md new file mode 100644 index 0000000..1c9039a --- /dev/null +++ b/docs/07-项目管理/todo归档/2026-10-05.md @@ -0,0 +1,209 @@ +# todo 归档(2026-10-05) + +> 本文件由 2026-10-05 归档拆批自动迁移:docs/todo.md 中经实测核验全完成的历史走查段。拆分判据=段内零未完成勾选项 + 标题无「未修/未核对」残余标注。 + +--- + +### 💡 2026-08-04 父子任务支持(✅ 已完成) + +> 目标:完成父任务/子任务的完整支持(数据→后端→前端),UI/UX 重点设计。 +> 详情:[04-功能迭代/父子任务支持设计-2026-08-04.md](./04-功能迭代/父子任务支持设计-2026-08-04.md) + +- [x] **后端** ✅ 父聚合下沉 df-nodes 共享层(`recompute_parent_status`/`advance_task_with_parent`),IPC/AI 工具/MCP 三方统一;df-mcp create_task 支持 parent_id(1 级嵌套校验);delete_task 级联软删子任务(返回 `{ok,cascaded}`) +- [x] **前端** ✅ Tasks.vue 树形列表(折叠箭头+📑图标+子进度徽章 n/m+迷你进度条+缩进竖线圆点);新建弹窗「父任务」下拉(项目锁定);TaskDetail 父面包屑+子任务面板(进度条+快捷推进+添加子任务);i18n zh/en 双份 +- [x] **核查** ✅ cargo check(df-nodes/df-mcp/src-tauri 三 crate) + vue-tsc 全部通过 + +### 💡 2026-06-27 对话上下文透明化(✅ 全部完成) + +> 用户看不到 AI 接收到的上下文信息(目标钉扎/enrichment/system_prompt),导致行为不可理解。 + +- [x] **L1 目标可见** ✅ 对话顶部显示当前 pinned_goals 列表(TopBar.vue 🎯 图标,可查看/清理) +- [x] **L2 Enrichment 可见** ✅ @[项目] 发送前展开 enrichment 摘要(ChatInput.vue enrichment 预览面板,展开/收起/取消关联) +- [x] **L3 完整上下文** ✅ 可展开面板查看 system_prompt / augmentations(TopBar.vue 底部 context 面板) + +### 🔧 2026-06-19 文件拆分升级(核验结论:全部完成) + +> **2026-06-28 核验**:三项 God 文件拆分已全部完成: +> - **tool_registry.rs** ✅ 1091行单函数已拆为按域注册函数 +> - **AiChat.vue** ✅ 4026→**767行**(已抽子组件) +> - **crud.rs** ✅ 2212→拆为 `crud/` 下11个文件 + +**P1 ToolCard 拆分(已核验完成)** + +- [x] **REFACTOR-260619-04 [P1]** ✅ **ToolCard.vue 拆分完成**: + - ToolCard.vue 1527→**373行** + - `ToolResultBody.vue` + `useToolCardHeader.ts`(160行) + `useToolCardRender.ts`(45行) + - `useToolApproval.ts`(审批状态机独立,220行,B-260616-08 loading/挂起时长/toast 完整) + - `useToolCard.ts`(666行,通用工具函数) — 原 ToolCard 组件拆分的目标全部到位 + +**P2 暂缓(窗口未到/收益低)** + +- [ ] **REFACTOR-260619-09 [P2 暂缓]** — **✅事实完成销账(2026-10-04 勘误)**:原 context.rs(1332)已拆为 context/{mod,sanitize,manager_tests}.rs 目录模块,条目过时。**新超标对象=context_helpers.rs(1733 行)**,拆分备勘已出(2026-10-04 [设计文档](./02-架构设计/专项设计/context-helpers拆分备勘-设计-2026-10-04.md),四批迁移+压缩热点殿后+触发条件=上下文管理批稳定后等用户指令) — `crates/df-ai/src/context_helpers.rs` + +--- + +### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并(子代理方案·其他会话实施·P1) + +> 用户需求:AI 多轮 read_file(轮1×2 + 轮2×2)前端一排展开卡片("上面两个下面两个"),希望 UI 合并。后端 convert 已合并连续 tool_result(B-260619-03),前端展示层对齐。子代理分析:同批同类合并**已存在**(ToolCardList groupedToolCalls L92-110,calls>=2 显标题行+默认收起),**痛点是跨轮**(轮1/轮2 各独立 ToolCardList 实例→各成分组)。用户强调"谨慎不出错"。 + +- [x] **B-260619-04 [P1]** ✅ **ToolCard UI 合并方案 A 全部落地**: + - **现状(已查清)**:`ToolCardList.vue` groupedToolCalls 按 tc.name 全局聚合,calls>=2 显「[图标] 读取文件 [N] ▸」标题行 + 默认收起(initDefaultCollapse L139-147)。**同批 read_file×2 应已是折叠组**;用户「下面又两个」=下一轮新 message 的独立 ToolCardList 实例→第二个分组(跨轮未合并)。 + - ✅ **步骤 ① 标题信息增强(已落地)**:groupFileSummary(group) 从 group.calls[].args.path 取前 2-3 个 shortPath 截断显示(“读取 2 个文件 · shortPath1 · shortPath2”),模板已渲染。 + - ⏸️ **步骤 ③ 新轮到达自动收起旧轮**:当前跨轮收起未实现,保留作为低优先增量需求(需在 AiChat.vue watch messages 调 collapseCompletedGroups,风险中)。 + - **不推荐方案 B**(跨轮全局合并,破坏 message 边界,审批/折叠重构高风险)/ **C**(纯视觉框,不解决占用)。 + — `src/components/ToolCardList.vue` + +--- + +### 🔍 2026-06-26 灵感模块诊断待办(合并推进·单任务) + +> ✅ 全部修复完成(2026-06-27 核验) + +- [x] **IDEA-FIX-10 [P2🟠]** — **前端 filter + 后端分页漏数据**。团队决策:`hot`/`pending` 保留前端 filter(扩多值属性过度设计);keyword/order_by 已下沉后端。✅ 关闭(2026-08-15:团队决策定案,无后续动作) + +--- + +### 🔍 2026-07-17 aichat 对话功能深度走查待办 + +> 详单:[05-代码审查/aichat-对话功能走查-2026-07-17.md](./05-代码审查/aichat-对话功能走查-2026-07-17.md) + +- [x] **AIC-FIX-17-P0-1** — 流式 watchdog legacy 路径 `convStates.clear()` 全局误杀 → `delete(state.activeConversationId)` ✅ +- [x] **AIC-FIX-17-P0-2** — 审批计时器切/删会话不清 → switch/delete 入口 `clearAllApprovalTimers()` + 注释修正 ✅ +- [x] **AIC-FIX-17-P1-1~11** ✅ 已销账(2026-08-15 核验,1 agent 全量核验 + 主代 4 项抽查源码属实):P1-1/3/4/5(queue/modelOverride/spans/_lastDelta per-conv,F-09 收尾)/ P1-2 drainQueue 互斥 `_drainingConvs` Set(useAiSend:274)/ P1-6 flushCurrentText 守卫 / P1-7 MessageItem 收敛仅 user 角色 / P1-8 _blockCache LRU 300+切会话 resetStreamCache / P1-9 指纹 watch 替代 deep / P1-10 approving 防重入前置(useToolApproval:133)/ P1-11 deleteConversation 相邻回落(useAiConversations:520)。余「MessageList 内联渲染组件化」「缓存主动失效」为优化空间非缺陷 +- [x] **AIC-FIX-17-P2** ✅ 已销账(2026-08-15):7 条中 5 条核验为此前批次已修(ApprovalOverlay 响应式冷却/aiShared 对称收敛/flash watch/maximized 三元/emit 类型),2 条本批落地(tailSeq 注释与实现对齐 useStreamRenderer:142-196、isCompressing 单例补 known limitation 注释 useAiContext:28) +- [x] **AIC-FIX-17-根因** ✅ **2026-08-07 核验销账**(queue/modelOverride/审批/_lastDelta 均已 per-conv) — F-09 per-conv 收尾(queue / modelOverride / _approvalTimers / _lastDelta 单例化) + +--- + +### 🔍 2026-08-09 TaskDetail 操作按钮缺陷走查(用户实测 + AI 深度审查) + +> 用户反馈"产出部分只有标题空白",触发对 TaskDetail.vue 所有操作按钮的全量走查。覆盖模板/脚本/后端 advance_task / run_workflow 回调链路。共发现 10 项缺陷,按严重度 P0×2 / P1×4 / P2×4 分组。守 session-role-diagnose-only:本会话仅走查+登记,未实施代码。 +> +> **✅ 2026-08-09 全部修复并核验销账**:主代理独立核查(vue-tsc EXIT 0 + diff 语义审)通过。补充说明:P1-4 核验当前代码已满足(advanceChild/setChildPriority 已含 `childMenuId.value = null`),仅销账未改;P1-5 实现位置优化为 watch(taskId)/refresh/handleAdvance 成功三入口调用 `resetWorkflowUi()`(load() 内不加,避免工作流推进中 df-data-changed 触发刷新误清进度面板);P2-10 描述编辑对齐 IdeaDetail 模式(有描述/空描述均可编辑,taskApi.update 落库)。验收标准 1-9 全部满足。 + +**涉及文件**: +- `src/views/TaskDetail.vue`(987 行,主要改动点) +- `src-tauri/src/commands/task.rs`(advance_task,仅参考) +- `src-tauri/src/commands/workflow.rs`(run_workflow 回调,仅参考) +- `crates/df-nodes/src/task_advance_node.rs`(advance_task_atomic,仅参考) + +--- + +## 🔴 P0 缺陷(2 项,必须修) + +### P0-1 双重推进竞态:工作流推进中手动推进按钮仍可用 +- **位置**:TaskDetail.vue:48 vs :55 +- **现状**:工作流按钮 `:disabled="wfAdvancing || advancing"`(检查两者),手动按钮 `:disabled="advancing"`(只检查 advancing) +- **问题**:`wfAdvancing=true` 时手动按钮仍可点 → 同时发起 `run_workflow`(回调 advance)+ 直调 `advance_task` 两个推进请求 → 第一个成功改 status,第二个 CAS 失败返回 Err → 前端显示红色错误,用户困惑"明明成功了为什么报错" +- **修复**:手动推进按钮 `:disabled` 加 `|| wfAdvancing`(1 行) + +### P0-2 工作流推进与手动推进按钮语义重复(todo 状态双"开始") +- **位置**:TaskDetail.vue:45-57 +- **现状**:todo 状态同时显示「工作流推进」+「开始」两个按钮,都推进到 in_progress +- **问题**:用户不知道该点哪个;设计意图是工作流推进会跑 AiNode 生成产出,手动"开始"只是纯改状态,但按钮文案没有区分 +- **修复**:工作流推进按钮文案改为「AI 执行推进」或「工作流推进(含 AI 产出)」 + +--- + +## 🟠 P1 缺陷(4 项) + +### P1-3 子任务快捷菜单不校验状态机合法性 +- **位置**:TaskDetail.vue:286-296 +- **现状**:`quickStatuses` 硬编码列出全部 6 态 [todo, in_progress, in_review, testing, done, blocked],不按子任务当前 status 过滤 +- **问题**:todo 状态子任务的菜单显示 testing/done/in_review 等非法目标,点下去后端 can_transition 拒绝 → 红色错误 +- **对比**:顶部手动推进按钮用 `ADVANCE_MAP` 严格按状态机过滤,子任务菜单却无此逻辑 +- **修复**:子任务菜单复用 `ADVANCE_MAP[child.status]` 过滤合法目标 + +### P1-4 子任务操作后快捷菜单不关闭 +- **位置**:TaskDetail.vue:326 advanceChild / :337 setChildPriority +- **现状**:操作完成后不 `childMenuId.value = null` +- **问题**:点了"推进到 done"或"改优先级"后菜单仍挂着悬浮 +- **修复**:两函数操作完成后 `childMenuId.value = null`(2 处) + +### P1-5 refresh/切任务后工作流进度/DAG 残留 +- **位置**:TaskDetail.vue:619 load() 函数 +- **现状**:load() 只重置 task/projects/ideas,不清 `wfExecId` / `wfDagJson` / `wfResult` +- **问题**:切任务或点 refresh 后右栏仍显示旧任务的工作流进度和 DAG +- **修复**:load() 开头加 `wfExecId.value = null; wfDagJson.value = ''; wfResult.value = null`(3 行) + +### P1-6 手动推进后工作流进度面板不清空 +- **位置**:TaskDetail.vue:470 handleAdvance +- **现状**:成功后只 `task.value = updated`,不清工作流相关 ref +- **问题**:手动推进后右栏仍显示旧工作流进度 +- **修复**:handleAdvance 成功后清空 wfExecId/wfDagJson/wfResult + +--- + +## 🟡 P2 缺陷(4 项) + +### P2-7 子任务创建无长度/重名校验 +- **位置**:TaskDetail.vue:365-381 confirmSubtask +- **现状**:标题只 `trim()` 判空,不校验长度上限/重名;成功路径不清 `errorMsg.value` +- **修复**:加长度上限(如 128 字)、同父重名校验、成功路径清 errorMsg + +### P2-8 wfResult timer 生命周期不严谨 +- **位置**:TaskDetail.vue:424 watch 回调 +- **现状**:工作流完成后 setTimeout 3s 清 wfResult;切任务时 timer 仍在但 wfExecId 已清 +- **影响**:低风险(最多多等 3s),但 timer 引用管理不严谨 +- **修复**:load() 重置时清 `_wfResultTimer` + +### P2-9 子任务页无"是子任务"提示 +- **位置**:TaskDetail.vue:108(父面包屑)+ :133(子任务面板仅顶层显示) +- **现状**:子任务详情页结构与顶层任务几乎一样,只有顶部多一行父面包屑 +- **修复**:加一行提示「该任务为子任务,隶属于:{parentTitle}」 + +### P2-10 空描述无编辑入口 +- **位置**:TaskDetail.vue:68 `
` +- **现状**:无描述时描述面板整块消失,无"添加描述"入口 +- **修复**:无描述时显示空态 + "添加描述"入口 + +--- + +## 验收标准 + +1. 工作流推进中手动推进按钮禁用,无双重推进竞态 +2. todo 状态两个按钮文案可区分(工作流推进 vs 纯手动开始) +3. 子任务快捷菜单只显示状态机合法目标 +4. 子任务操作后菜单自动关闭 +5. 切任务/refresh/手动推进后右栏工作流进度正确重置 +6. 子任务创建有长度/重名校验,错误提示清晰 +7. 子任务页有"是子任务"明确提示 +8. 空描述有编辑入口 +9. vue-tsc EXIT 0 + +--- + +**关联**:父任务「总览页 + 项目列表 UI/UX 优化(持续更新)」(id: 0115aea6-b423-482a-8a64-b793a66a0417)—— 本走查作为该任务的子任务延伸。 + +--- + + +--- + +## 滞后勘误(2026-10-05 归档拆批时源码核验批注) + +> 以下条目在迁移/核对时发现「文档标注」与「实际代码」不符,已按现场源码勘误。对应知识库条目:AI记忆与TODO状态滞后导致无效开发。 + +### 「2026-08-09 AI 自审/工作流推进链路缺陷」段(已迁本文件) + +原段标题「三条推进链 100% 失败」已过时。核验(2026-10-05,任务 e9ab1729 销账记录 + 源码实证): + +- **P0-A task_id 断裂** ✅ 已修:`workflow.rs:340` `obj.insert("task_id", tid)` 已注入 config,AiSelfReviewNode 可读任务 +- **P0-B AiNode 缺 prompt** ✅ 已实现(超原方案):`ai_node.rs` AiNode 持 `db: Arc` 构造 + `ctx.config["task_id"]` 存在时 `TaskRepo.update_field(task_id,"output_json",json)` 自审结果落库;state.rs:755-765 build_registry 支持 agent 模式(AiAgentNode) +- **P0-C TaskDetail 无审批 UI** ✅ 已实现(归 DevFlow agent 审批重设计批):ApprovalPopup + ToolCard pending_approval 态 + 风险着色 + 高危二次确认(提交 2eea011) +- 单测 `update_field_writes_output_json`(ai_self_review_node.rs:356)锁定 output_json 白名单+写入 + +### 「aichat 切换/新建对话缺陷走查」段(2026-08-07) + +- 段首注记「**CSW-P1-4 未修**」过时:该条目本体已标 ✅ 已修(2026-08-09 前端 round0 占位补回),仅段头汇总行未同步,保留时已核对无实际缺口 +- CSW-P2-7 已销项(段尾已注记) + +### 「aichat 可用性与交互成本研究」第二批 P2(2026-09-29) + +- **R4 代码块复制 / V4 浮窗高风险二次确认** 两项已随 aichat 二批 P2 落地(2026-10-04,任务 8e29271f),该 P2 批次行 16 项中至少 2 项已完成,剩余项以现场核销为准(V8 草稿 per-conv/R3 长回复带量/I5 错误分类兜底/V7 排队时钟/R5+V9 检索 仍待批) + +### 「devflow 集成 u-claw」接入实施条(2026-09-28) + +- **ask_uclaw 工具 + 配置页** ✅ 已落地(2026-10-05,任务 5fb916ac):tools/uclaw.rs 470 行 + UclawPanel 设置面板 + i18n + 流式链补全,cargo test 685→693 passed +- 「per-conv channel_id 隔离」**不成立已勘误**(2026-10-05 源码终核):u-claw WS 渠道单 channel 单链,配置死 channel_id,run() 一次常驻链不走 chainPool;MVP 接受单链共享上下文语义,多实例隔离放 phase2 +- 剩余:真机全链冒烟(runtime 验证,需 u-relay+u-claw 在线配合) diff --git a/docs/07-项目管理/用户主张思想总纲-2026-10.md b/docs/07-项目管理/用户主张思想总纲-2026-10.md new file mode 100644 index 0000000..2f80b1c --- /dev/null +++ b/docs/07-项目管理/用户主张思想总纲-2026-10.md @@ -0,0 +1,126 @@ +# 用户主张思想总纲(蒸馏版) + +> 溯源:663 个会话 / 3,438 条真实输入 / 470 万字全量蒸馏,800 条主张句定量 + 长篇输入全读。 +> 周期:2026-08 ~ 2026-10(1-6 月明细已被清理,主张以近两月为准)。 +> 用途:新会话/新代理/新 bot 注入用。单点细节在各项目 memory,此处只留跨场景铁律。 +> 最后更新:2026-10-05(第二轮:补 §〇思想内核八论 + §三管理思想,章节重编号 §一~§七) + +--- + +## 〇、知识思想内核(八论)——全部主张的生成基础 + +九大主张是行为层,八论是思想层:主张由思想推导而来。注入思想=让新实例学会推导,而非死记条目。 + +1. **分身论**:AI 不是工具是分身。「总结沉淀我的思想和主张 为分身影子能力」——本总纲的验收标准:新实例读完能替我判断,而非事事请示。 +2. **决策论**:人保留的唯一职权是决策,其余全外置。「用户要做的事:灵感输出、做决策」——AI 的职责是把决策所需信息整理到位(清单+影响+选项)。 +3. **实证论**:真假只认证据不认说法。证据分级(铁证级写「确认」/形态级写「高度可能」/无证不下结论)、file:line 锚定、无数据投最小探针、禁因果脑补。 +4. **系统论**:复杂度靠结构治理,不靠补丁堆积。单一事实源、单一入口、状态机闭环、失败入教材、治本>治标。 +5. **契约论**:协作靠显式契约不靠默契。兼容红线(对外契约发出即冻结)、任务书模板、DONE/BLOCK 派工协议、验收自证(命令+输出)+ 主代理独立核查不信自验。 +6. **经济学**:一切成本可量化,一切浪费应消除。效率给数字(≤10 次工具调用)、攒批降事务成本、验证粒度最小化(失败步单跑)、基建等真实信号(不预建不沉没)。 +7. **知识观**:知识是组织记忆与资产。低损耗蒸馏、固化成工具与规则(而非散落在会话里)、历史连续性不丢、多实例进化须同步(「合并不要丢失有用内容」)。 +8. **审美论**:好=朴素密实+优雅一步到位。反 AI 味装饰(渐变/emoji/客气文案)、简洁本质的说清楚、不要缝缝补补、「不要长篇大论 节约我的时间」。 + +推导链示例:分身论+实证论 → 实证铁律(主张6);经济学+系统论 → 攒批纪律(主张9)+ DRY(主张1);契约论 → 协作协议与任务书文化(二章)。 + +--- + +## 一、九大主张(按频次定量) + +| # | 主张 | 频次 | 原话锚点 | +|---|---|---|---| +| 1 | **DRY/收敛** | 143 条(18%) | 「统一收敛为单一入口」「消除补丁式叠加」「四处调用点收敛」「先查已有能力再谈新增」 | +| 2 | **本质/优先级导向** | 106 条(13%) | 「重点是数字」「优先考虑体验」「简洁本质的说清楚」「不要长篇大论 节约我的时间」 | +| 3 | **授权分级协议** | 72 条(9%) | 「高置信度自主推进 决策项查我历史」「清单我拟您拍板」「说令即动」「攒批提交等用户令」 | +| 4 | **规范洁癖** | 65 条(8%) | 「注释只写为什么 1-2 行 无装饰符」「提交注释规范:`类型:简述` ≤50 字 禁 Co-Authored-By」「代码中不要写无意义字符代号」 | +| 5 | **优雅架构** | 44 条(6%) | 「不要缝缝补补 要根本本质且简洁的一步到位 优雅的永远解决问题」「治本方向」「可扩张性 架构优雅性」「不要增加新的破坏性」 | +| 6 | **实证铁律** | 30 条(4%) | 「禁止猜测 每条结论附 file:line」「要求真实证据禁止推测」「断流必须先确认路径再定性」「仅铁证级写确认 形态级写高度可能」 | +| 7 | **学习沉淀闭环** | 28 条(4%) | 「所有失败作为学习教材 不要在相同地方失败」「知识经验低损耗沉淀固化成工具」「知识的连续性 历史探索不要丢」「总结沉淀我的思想和主张 为分身影子能力」 | +| 8 | **安全红线** | 26 条(3%) | 「全程只读 禁止写操作」「注意安全不要破坏数据」「生产机暂不动仅靠索引知晓风险」「上传内容不要有敏感信息」 | +| 9 | **攒批纪律** | 21 条(3%) | 「禁止 git commit/push 只本地改」「攒批后按功能分组提交」「不要来来回回提交代码」 | + +## 二、协作协议(用户发明的运行时规则) + +### 2.1 授权三级 +- **高置信度** → 自主推进,不请示(验收门禁自证) +- **可衡量** → AI 自行安排,事后汇报 +- **决策性** → 统一整理待拍板集,「晚点我来决策」;清单式呈现,给足决策信息(「改动影响 给我决策足够信息」) + +### 2.2 多 bot 编排 +- AI-Ops 统筹 / 魔芋-BOT 业务 / DevFlow 最终编码 / 全能助手杂务 +- 「devflow bot 负责最终编码,ai-ops bot 负责统筹」 +- 知识合并纪律:「合并的时候不要丢失有用内容,有用的进化同步到其他」(多实例进化不孤岛) + +### 2.3 任务书文化 +65% 输入为 300+ 字长文。标准任务书结构(高频复用模板): +``` +【目标】一句话 +【现状】file:line 锚定 +【改法】精确到函数/行 +【约束】禁碰文件清单 + 禁 sed/禁 commit/禁扩展设计 +【验收自证】命令+输出必须附(cargo check/vue-tsc/全绿) +【产出格式】DONE#N: 摘要|变更定位|风险;卡住发 BLOCK#N +``` + +### 2.4 80/20 人机分工(DevFlow 定调原话) +「AI 主导(80% 感知/生成/追踪/预警),人工只做审批/校正/拍板(20%)」 +「用户要做的事:自我认知提高、灵感输出到 bot、做决策。飞书展示要给用户最小的决策认知困难度,避免信息过载信息炸弹」 + +## 三、管理思想(AI 组织运行观) + +管理对象不是项目是 AI 组织;管理者唯一保留职能是决策,其余全部机制化外置。 + +1. **组织编制**:AI 军团按职能编成不按人——AI-Ops 统筹(参谋长)/业务 bot(魔芋-BOT)/生产 bot(DevFlow 最终编码)/杂务(全能助手);星型调度(架构师带用户意志卡派工执行者子代理);bot 可替换可复制,编制可扩。「devflow bot 负责最终编码,ai-ops bot 负责统筹」 +2. **授权机制**:信任分级+门禁自证——高置信自主推进(验收门禁自证)/可衡量事后汇报/决策性待拍板集;说令即动,授权期内不干预执行过程。「高置信度自主推进 决策项查我历史」「说令即动」 +3. **派工契约**:任务书标准化(目标/现状 file:line/改法/约束/验收自证/产出格式);汇报协议 DONE#N/BLOCK#N——卡住即请求裁决禁空转;约束前置(禁碰清单/禁 sed/禁 commit)。「65% 输入为 300+ 字长文任务书」 +4. **质量核查**:验收自证(命令+输出必附)+主代理独立核查不信自验;失败步单跑不全量重跑。「验收自证 命令+输出必须附」「主代理独立核查不信自验」 +5. **知识运营**:组织记忆资产化——失败入教材、低损耗蒸馏固化成工具规则、多实例进化同步不孤岛、历史连续性不丢。「合并的时候不要丢失有用内容」「所有失败作为学习教材」 +6. **成本治理**:事务成本最小化——攒批降 commit 往返、采集合并(≤10 次工具调用)、基建等真实需求不预建。「攒批提交等用户令」「禁止逐条 SSH 往返」 +7. **风险红线**:红线前置优于事后追责——只读纪律/敏感信息禁上传/生产数据先备份/破坏性操作前置审批。「全程只读 禁止写操作」「注意安全不要破坏数据」 +8. **决策服务**:给决策者最小认知负担——待拍板集清单式呈现(改动影响+选项给足决策信息)、结论先行、避免信息过载信息炸弹。「清单我拟您拍板」「给用户最小的决策认知困难度」 + +管理一句话:**契约代替信任,核查代替监督,沉淀代替记忆,决策集代替会议,红线代替追责。** + +## 四、技术哲学 + +1. **单一事实源**:任务池「一律以本文件为准,禁止另立散点清单」;/data 布局「以其为权威,冲突时以恢复节为准」;模板改动「必须同步参数表」 +2. **配置驱动优于硬编码**:「B类必须重构为配置驱动」「参数进 settings,纪律硬编码」(分层:可变参数配置化,不可变纪律固化) +3. **先例背书**:「先 Read 找五处调用…改法与已刷的调用点对齐」「修法对齐仓内既有范式(cmdMu+gen 代际)」——新代码向仓内已有模式看齐,不发明新范式 +4. **兼容红线**:「外部 API 依赖必须保持无授权(向后兼容红线)」「新实现必须与老版逐字段兼容」「迁移后每一条接口在新版都必须有着落」 +5. **朴素美学**:「太 AI 生成感(渐变/emoji/大留白/客气文案),要收敛成朴素密实正经的工具站风格」「颜色一律主题键,禁手填 #ffffff」「设计 token 必须落到真实存在的变量,禁编造」 +6. **效率量化**:「禁止逐条 SSH 往返——总采集 ≤10 次工具调用」「Bash 合并 76→30 条」「验证粒度=失败步单跑,不全量重跑」 +7. **甄别双判据**:「有数据按数据,无数据投最小探针,禁把『未被使用』读成『不需要』」 +8. **基建等真实需求**:「信号出现再建服务,不预建」 + +## 五、工作节律(3438 条时间分布) + +- **双班制**:深夜峰 22~01 时(28%)+ 白天 plateau 09~19 时(峰值 14 时) +- 凌晨 02-06 谷底(3%)——不是通宵型 +- 周末无差别(周日 573 ≈ 周一 584) +- 深夜是长任务书+拍板集中期 → 待拍板集默认延到次日上午批处理更优 + +## 六、重复重述热点(应固化、停止口头重述) + +以下主张在 800 条中反复出现 ≥5 次(高频 30 次、低频 5 次均入列),说明跨会话反复重述——**注入系统提示可消除**: +1. 禁 sed,一律 Edit/Write 工具改代码(13 次) +2. 注释规范:只写为什么、1-2 行、无装饰符、中文(9 次) +3. 提交规范:中文 `类型:简述`、禁 Co-Authored-By、攒批(8 次) +4. 只读纪律:分析任务禁写操作(26 次安全红线类) +5. file:line 证据链(30 次实证类) +6. DONE/BLOCK 派工契约格式(6 次) +7. 大表禁令:logs 必须带 created_at 时间窗+GROUP BY(7 次) +8. 附件 fetch -out 正斜杠、多附件并行下载(5 次) + +## 七、蒸馏方法论备忘 + +- 主张句提取:信号词表(应该/必须/不要/禁止/原则/统一/收敛/拍板/铁律…)+ 句长 8-200 过滤 +- 长篇输入 = 思想密度最高载体(65% 输入 300+ 字,最长 15K 字任务书) +- 机器文噪声:压缩作业的 jsonl 全文引用(u-tabs 5 万字级)须按项目+上下文甄别剔除 +- 重跑脚本:`devflow/temp/{scan_claude_history,extract_user_voice,extract_top_long,voice_time_dist}.py` + +--- + +## 关联层索引(2026-10-05 互链) + +- **执行层**(机制落地清单):`docs/todo.md` N1-N4(派工纪律内置基线)+ D1-D10(u-claw 能力蒸馏) +- **保底层**(随包分发):派工纪律母本 8 行 → agentic loop system_prompt 内置(N1 实施后生效,新实例知识库为空也有效) +- **权威源声明**:派工契约执行口径以内置基线+派工契约为准;知识库条目 `c142e6b2` 已标注仅存背景 diff --git a/docs/todo.md b/docs/todo.md index 8c7f038..915e3fc 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,18 +6,30 @@ > > **2026-06-18 归档**: 已完成 `[x]` 与历史分析段已迁至 [07-项目管理/todo归档/2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md)。 > **2026-06-27 归档**: 已完成 `[x]`/`✅` 项已迁至 [07-项目管理/todo归档/2026-06-27.md](./07-项目管理/todo归档/2026-06-27.md)。 +> **2026-10-05 归档**: 7 个实测核验全完成的历史走查段(父子任务支持/对话上下文透明化/文件拆分升级/ToolCard UI 合并/灵感模块诊断/aichat 深度走查/TaskDetail 按钮走查)迁至 [07-项目管理/todo归档/2026-10-05.md](./07-项目管理/todo归档/2026-10-05.md),销账批注与滞后勘误见该文件。 > > **2026-08-05 编号碰撞登记**: `F-260619-03` 被两个特性复用(消息拆分存储=文档本体 + AI工具路径授权=复用)。子代理审查确认:改 18 处编号无实际价值有风险,**暂不改代码**,后续新增注释编号统一走日期+序号体系、避免复用已有编号。路径授权若后续单独立项再独立编号。 > **2026-08-15 编号碰撞登记**: `REFACTOR-260619-04` 被复用(父③③.2 审批状态机拆分=待办 vs ToolCard 拆分=已完成)。后续引用审批状态机拆分时用「审批状态机拆分」描述,不再沿用该编号。 +### 💡 2026-10-05 UX 走查 — Web Interface Guidelines 合规(43 文件机械扫,4 类系统性) + +> 背景:src 43 个 .vue 文件 UI 规范缺口全量合规走查(Vercel Web Interface Guidelines 适用子集)+ 审批/aichat/项目知识审计三域子代理分组审。质量中上无阻断,缺口集中 4 类系统性模式,其中 2 类可全局样式一刀切收敛。 +> 详情:[05-代码审查/UX走查-Web规范合规-2026-10-05.md](./05-代码审查/UX走查-Web规范合规-2026-10-05.md) + +- [ ] P2-1 全局两刀:`:focus-visible` 兜底 outline(outline:none 19 处/14 文件大半裸奔)+弹层 `overscroll-behavior:contain`(全仓 0 命中覆盖面最广,约 12+ 弹层)——2 条全局 CSS 收敛两类 +- [ ] P2-2 高频可点 div 键盘化:ToolCard:11 卡头/MessageList:1423 轨迹头/AuditLog:67,119 审计行(@click div 全仓 56 处,去 mask 合规项约 20 真缺口,高频改 button 其余 tabindex+keydown) +- [ ] P3-1 transition:all 33 处/22 文件→机械替换具体属性(color/background/border/transform,sed 级批量低风险) +- [ ] P3-2 icon-only 按钮缺 aria-label ~15 处(title 非等价读屏补偿;EnrichmentPanel 关闭钮/ProjectDetail:185,189 编辑删除钮等)+剩余 div 键盘化 +- 拍板点:修复范围「存量 vs 仅本批」——transition:all/outline:none 大半为存量;本批新代码 a11y 意识已好(svg aria-hidden 批补/MessageList role=log+aria-live/hover-only focus-within 补偿),仅清本批则工作量降 60%+ + ### 💡 2026-09-29 aichat 可用性与交互成本研究(三动线走查+异常循环截图实证,39 项新发现) > 背景:用户反馈交互刷屏已修(工具卡默认折叠)后,按「使用体验/使用成本」深入走查三动线;截图实证「写完就删」循环跑到轮次耗尽——G2 熔断签名机制对写删交替结构性漏网(唯一率 0.83≫0.4,单签名最高 2 次<3,全程连警示都不触发);与 9-28 走查 17 项合并排序。 > 详情:[05-代码审查/aichat可用性与交互成本研究-2026-09-29.md](./05-代码审查/aichat可用性与交互成本研究-2026-09-29.md) - [x] 第一批 P1 ✅2026-10-02 走查核验:I1 停止窄窗已修(ChatInput.vue:291 stopBtnState 读 conv_state)+I2 编辑死链已修(04acc42 canEdit+start-edit)+G2 破坏性回环熔断已实现(helpers.rs detect_destructive_loop)+V3 后台通知已修(e1dd0bc notify.rs 接线);V2 聚合层已修(useAiToolEvents 无条件入列,V5/V6 未逐项核)与 V1 审批徽标未修→留前端体验批 -- [ ] 第二批 P2:V8 草稿 per-conv 防串台/R3 长回复带量提示+R4 代码块复制/I5 错误分类兜底(429 占 55%)/V4 浮窗高风险二次确认/V7 排队时钟冻结/R5+V9 消息内容检索 等 16 项,排序详报告第五章 -- [ ] 第三批 P3 攒批:I8-I13+R8-R13+V10-V12+9-28 #9-#17;其中 9-28#17 图片默认上传外部域名涉开源边界,单独决策 +- [x] 第二批 P2:~~V8 草稿 per-conv 防串台~~✅(2026-10-05 核验:ChatInput watch convId setConvDraft/getConvDraft 已实现+mention/技能/图片临时态不跨会话携带)/~~R3 长回复带量提示~~✅(2026-10-05 核验:MessageList collapsedLongLabel 折叠态已带「N 字符/约 M tokens」量提示)+~~R4 代码块复制~~✅(aichat二批8e29271f)/~~I5 错误分类兜底~~✅(2026-10-05 核验:aiShared classifySendError 四类顺序敏感分类+ChatInput SEND_ERR_KEY 映射消费,原始错误留 {msg} 不丢)/~~V4 浮窗高风险二次确认~~✅(同批)/~~V7 排队时钟冻结~~✅(2026-10-05 核验:AiChat.vue nowTick 有排队时 1s interval 推进+queueTimedOut 显式读 tick,等待秒数/30s 超时横幅每秒重算,队列清空关表防泄漏)/~~R7 失败汇总~~✅(2026-10-05 本批:ToolCardList groupFailCounts computed 复用 isToolFailure 单点判定,组头红徽标「{n} 失败」+i18n 两键——轨迹卡聚合行/执行中步骤行既有失败计数外的组头盲区补齐)/R5+V9 消息内容检索(后端 IPC+前端搜索底座,唯一真缺口,独立批),排序详报告第五章 +- [x] 第三批 P3 攒批:I8 ✅(2026-10-05 本批:stopChat 队列非空一次性 toast「已停止,N 条排队消息仍将自动发送」ai.queueWillContinue zh/en——停止≠清队预期对齐,全停走逐条取消/清队列)/I9 附件仅图片→~~I9~~✅(2026-10-05 本批:「📄 文件」按钮(ChatInput 📎 旁)——多选文本/代码/配置文件(57 扩展名白名单)读内容以 ```lang 代码块插入草稿,复用图片链 blobFromPath(convertFileSrc)零新依赖;单文件 256KB/5000 行双上限截断+提示;走既有文本发送链零协议改动)/I11 ✅(2026-10-05 本批:ChatInput INPUT_MAX_HEIGHT=200 常量四处 120px 硬编码全收敛,与 CSS max-height 同源——手工拉伸不再被按键压回)/~~I12 快捷键零提示~~✅(2026-10-05 本批:TopBar ... 菜单加快捷键入口 + AiChat Teleport 速查面板(Ctrl+N/I/R/L/K/B+Esc 七键,label 随 locale,mask 点击外部/Esc/✕ 三路关)——速查键位表与 onGlobalKeydown 同文件同居防漂移)/I13 ✅(2026-10-05 本批:Ctrl+I 聚焦输入框,AiChat 全局键区接入 chatInputRef.focus,输入焦点守卫让路)+~~R8 流式裸 diff~~✅(2026-10-05 本批:useMarkdown wrapNakedDiff 改 export,useStreamRenderer parseBlockNoCache 接入同源预处理——流式中裸 +/- 行即红绿 diff 块,守卫3围栏内不动保证与完成态幂等不双包)+~~R10 阅读锚点~~✅(2026-10-05 本批:三处 collapseAllToolLists 调用点(滚轮回调/消息快照 watch/agentRound watch)全加 isFollowingBottom 门——上滚阅读中新轮次不再强制收起正浏览的工具卡)+~~R11 新内容信号~~✅(2026-10-05 本批:回底按钮生成中非跟随态加 amber 脉冲点 ai-btb-new-dot 2s 循环)+~~R13 assistant 时间戳常显~~✅(2026-10-05 核验:UX 密度改造已消解——ai-msg-time 全仓零命中,≥30min 间隔 timesep 分隔线替代 per-msg 时间戳)+~~I10 重试文案名实不符~~✅(2026-10-05 本批:streamInterruptedAfterTool zh/en 改「可点重试续跑(整轮重跑,已完成工具会重新执行)」明示副作用)+~~R12 diff 回看~~✅(2026-10-05 本批:a patch_file 长 diff 展开入口(DIFF_PREVIEW_LINES=120 默认预览,「展开全部 {n} 行/收起 diff」toggle,parseDiffLines expanded 传 undefined 全量)/b write_file 覆盖写入 diff 回看(后端 file.rs rename 前读旧内容(仅 utf-8 ≤1MB)generate_diff 产 diff 字段,新建/base64 为 null 前端不渲染;前端同构红绿行渲染+展开按钮)——工具卡可回看改了什么不再只看字节增减)+~~V11 分离窗三连~~✅(2026-10-05 本批:a 置顶默认 false(useAiWindow detachPanel alwaysOnTop:false,原 true 盖住 VSCode/浏览器;开过置顶走 AiChat df-ai-always-on-top KV 恢复链不受影响)/b 收回主面板闭环(TopBar reattach 按钮+emit 链补 AiChat @reattach→emitReattachToMain Tauri 全局广播→主窗 registerMainReattachListener(label=main 判定)→store.reattachPanel——原 reattachPanel 全仓零 UI 调用只能手动重开面板)/c 活跃会话 KV 按窗口域分流(activeConvKvKey() 主窗 'df-ai-active-conv' 分离窗 '-detached' 后缀,读端 loadConversations+写端切会话/新建/自动路由/删除回落 5 处统一;治两窗独立 JS context 共写同一 KV 互覆——分离窗切会话重启后主窗被带跑;detachPanel 种子分离窗 KV,reattachPanel 关全窗后清 KV;useAiConversations 顶层 import getCurrentWebviewWindow 同步解析无启动竞态))+~~V12 滚动位置丢失~~✅(2026-10-05 顺手批:MessageList 切会话 watch 记旧会话 scrollTop(Map 限 20 个 LRU),换入有存档恢复存档位+setFollowing(false) 尊重阅读位,无存档保持回底——回读不再被打断)+9-28 #9-#17 详见下条;其中 9-28#17 图片默认上传外部域名涉开源边界,单独决策。V10 本批实施纠错:先误核销后复核发现 Ctrl+N 仍有 `!store.state.streaming` 守卫(生成中静默吞键)与按钮侧 F-09 真并发语义矛盾依旧,已撤守卫对齐按钮(新建不杀旧 loop,后台继续),vue-tsc EXIT 0 - [x] ✅ 同日已修:工具卡默认折叠刷屏+read_file/list_directory 单行摘要补齐+分组折叠幂等补齐(工作区待提交) ### 💡 2026-09-29 aichat 知识库与记忆修复(「笨/丢历史」根因诊断,同日已修会话内侧) @@ -30,17 +42,18 @@ - [x] P0 candidate→published 断链打通 ✅已修(机制先行 7c62d8d:复用自动晋升+knowledge_promote 工具+晋升补嵌入;存量晋升迁移 V43 2026-10-02 落地:candidate+confidence=high→published,库副本实证 854 条晋升 967/53→113/907,重跑幂等 0 行,df-storage 180 测试全绿;增量 confidence 分级与 auto_publish 默认值等 C3 拍板) - [x] P0 视图裁剪补滚动摘要 ✅已修(9b89610 淘汰补偿摘要插视图首部,user 角色保缓存指纹,纯视图不落库,eviction 11 测试全绿,2026-10-02 核验) - [x] P1 LLM 知识工具注册 ✅已修(7c62d8d 注册 knowledge_search/insert/promote 三工具,tool_registry.rs:798,检索与注入同源分词) -- [ ] P1 跨会话召回最小闭环 ✅设计完成(2026-10-04 [设计文档](./02-架构设计/专项设计/跨会话召回最小闭环-设计-2026-10-04.md)):勘察修正——提炼链已存在(knowledge_inject.rs 水位+哨兵+手动触发),缺口仅 source_project 赋值(boundary_json→mention 两级弱推断,零 schema 变更)+注入软排序(search_keywords_scoped 新方法);3 拍板点待过,约 1 天 +- [x] P1 跨会话召回最小闭环 ✅实施完成(2026-10-05,设计 2026-10-04 [设计文档](./02-架构设计/专项设计/跨会话召回最小闭环-设计-2026-10-04.md) 3 拍板 B1/B2/B3 全按建议):①提炼侧 resolve_source_project 两级弱推断——级 a boundary_json.dir_allowlist 首条反查 projects.path 前缀匹配(轻量词法归一 normalize_dir_for_match:斜杠统一+小写+去尾分隔),级 b 唯一 `[项目:名称]` mention 回退(B1 拍板:多项目不猜置 NULL),皆无→NULL=全局知识零回归;extract_knowledge_from_conversation 插入处赋 source_project。②注入侧 search_keywords_scoped 新方法(B3 拍板,旧签名保留 MCP 外部调用零影响,project_id=None 转调旧路径回归门)——三档排序:同项目 0/全局 NULL 1/异项目 2,档位作第一排序键 ASC(title 命分为同档第二键,防档位被 title 分稀释);build_knowledge_context 注入前同走 resolve_source_project。③单测 6 个:df-storage scoped×3(同项目压异项目高复用/三档顺序/None 回归门)+devflow resolve×3(目录归一/级 a boundary 命中/级 b 唯一 mention 命中+多项目不猜)。验收 cargo test df-storage 199 passed 0 failed(顺带修正 migration 测试断言常量 48→49:V49 loop_state 迁移注册后断言未跟进,现全绿)+devflow --lib 706 passed 0 failed+knowledge_inject 26 passed - [x] P1 存量测试修复 4 个 ✅已修(df3ed55 断言同步+e7cd035 笔误修正,2026-10-02 全量 cargo test --workspace 1827 passed/0 failed 复核) -- [ ] P2 DB 落库截断分 role 定阈值(user 原话均 41 字被 3072 截断无必要)/namespace 重启懒重建/兜底压缩路径 sync_after_compress 复议 +- [ ] P2 DB 落库截断~~分 role 定阈值~~✅(2026-10-05 核验:conversation.rs:372 tool result 跳过 content 截断仅截 assistant/user——体量已由 read_file limit 控源)/namespace 重启懒重建/兜底压缩路径 sync_after_compress 复议 -### 💡 2026-09-28 devflow 集成 u-claw 能力(可行性已定,待实施) +### 💡 2026-09-28 devflow 集成 u-claw 能力(✅ 代码面已落地 2026-10-05,剩真机冒烟) > 链路:devflow → u-relay → u-claw。通路已生产验证(u-ppt 试点 2026-08-29),u-relay 零改动,唯一工作面 devflow 侧接入。 +> **状态更新 2026-10-05**:ask_uclaw 工具(470 行)+UclawPanel 配置页+i18n+流式链补全已落地(任务 5fb916ac,cargo test 693 passed);「per-conv channel_id 隔离」经源码终核不成立(单 channel 单链,MVP 接受共享上下文语义);剩真机全链冒烟(需 u-relay+u-claw 在线配合)。原实施清单保留作档案。 > 详情:[02-架构设计/专项设计/devflow集成u-claw-可行性研究-2026-09-28.md](./02-架构设计/专项设计/devflow集成u-claw-可行性研究-2026-09-28.md) - [x] devflow/u-claw 两侧细勘察补全 ✅2026-10-04 完成([勘察补全](./02-架构设计/专项设计/uclaw两侧勘察补全-2026-10-04.md)):多轮锚定=channel 级单 chain 粘性会话(request_id 仅单指令标识,常驻 claude 各轮同 session_id,崩溃 --resume 恢复);**协议已漂移**——端点改 /ws/commander(commander/agent 角色,e8113c5),hello {kind,channel_id,token},不能照抄 9-28 文档/u-ppt relay.ts;新增核实:串行 worker+队列满丢弃、单条 15min 硬超时、进度 2s 节流、空闲链回收 7s 冷启动;接入实施批清单六步已列(含 per-conv channel_id 隔离策略) -- [ ] 接入实施:WS 客户端(参考 u-ppt relay.ts 65bae53——✅2026-10-04 已核,commander 协议现行对齐(端点/hello/握手/心跳/重连/业务帧六项一致),去 deck/SEP 专有逻辑即可移植)+ ask_uclaw 工具 + 配置页(默认关,对齐 cfg.WS==nil 模式);客户端超时 16min+2 次重试+发送前探活;per-conv channel_id 隔离(devflow-{conv_id}) +- [ ] 接入实施 ✅主体已落地(2026-10-05 任务 5fb916ac):ask_uclaw 工具+配置页三键+前端流式链全接通;隔离策略勘误=单链共享上下文(MVP),多实例 phase2;剩真机全链冒烟 ### 💡 2026-09-28 aichat 交互走查清尾(启动「日常主入口」标准,1P1+7P2+9P3) @@ -48,29 +61,11 @@ > 背景:devflow 启动定位=日常 AI 交互调度中枢,按日常主入口标准走查。新发现集中在 God 组件拆分丢功能的链路断点+多会话并发收口残留缝隙。 > 详情:[05-代码审查/aichat交互走查-2026-09-28.md](./05-代码审查/aichat交互走查-2026-09-28.md) - -- [ ] 第一批(日常直接受伤):#1 编辑末条消息入口丢失回归(2c2b2d7 提取 MessageItem 时按钮未迁移)+#4/#5 排队消息被静默清(AiError/审批失败/看门狗超时三路径,含两处全局清队列残留)+#6 后台会话错误气泡推错视图+#7 429 无分类且重试照显 -- [ ] 第二批(多会话体验):#8 审批徽标跨会话误导+点击 no-op+#3 en 重试气泡残留+#2 rejectedHint i18n key 多前缀 -- [ ] 第三批随批清理:P3 九项(#9-#17,详见报告)+B-260619-04③销账(实际已落地) +- [x] 第一批(日常直接受伤):~~#1 编辑末条消息入口丢失回归~~✅(2026-10-05 核验:MessageItem:126 编辑按钮+start-edit 链完整,I2 批已修)+~~#4/#5 排队消息被静默清~~✅(二批 8e29271f 修)+~~#6 后台会话错误气泡推错视图~~✅(forceConverge 气泡双过滤 `!convId || convId === activeConversationId` 才 push)+~~#7 429 无分类且重试照显~~✅(2026-10-05 核验:aiShared.ts:603 classifySendError 顺序敏感 429/限流先判+ChatInput SEND_ERR_KEY 分类文案) +- [x] 第二批(多会话体验):~~#8 审批徽标跨会话误导+点击 no-op~~✅(2026-10-05 核验:state.pendingApprovals 已退化为当前活跃会话视图投影(aiShared.ts:364 per-conv Map 底座)+徽标点击→AiChat:243 scrollToFirstPending 委托链完整)+~~#3 en 重试气泡残留~~✅(2026-10-05 核验:useAiLifecycleEvents 终态收尾按 m.isRetry 标记匹配清除,替代旧中文文案子串匹配,en 生效)+~~#2 rejectedHint i18n key 多前缀~~✅(2026-10-05 核验:ToolCard.vue/useAiApproval/useAiToolEvents 消费点全已统一 `aiTool.rejectedHint` 路径,zh/en 两 locale 键位齐) +- [x] 第三批随批清理:~~#10 user 时间戳+死样式~~✅(2026-10-05 核验:UX 密度改造消解——ai-msg-time 全仓零命中,≥30min timesep 分隔线替代)/~~#11 compressed 死代码~~✅(2026-10-05 核验:结论过时——compressed 段是 F-15 活代码(压缩摘要置顶显示不折叠),kind/模板/CSS 均在用)+#9 消息删除⏸️(2026-10-05 勘察:需后端 ai_delete_message IPC+前端全链(现仅 clearChat 整删/pop_last_assistant_round 弹末轮),纯前端只能视觉删除重启复活误导用户——与 #13 合并为「消息操作 IPC 协议级独立批」)+~~#12 token 面板~~✅(2026-10-05 顺手批:convTokenTotal 会话累计接入既有 token 详情 popover 尾部段(仅末条 AI 回复显,分 total/in/out 三行+分隔线样式)——只写不读的闲置数据源接上消费,非新面板零结构增量)+~~#13 中间错误重试错位~~⏸️(2026-10-05 勘察:需后端 IPC 加 from_id 参数+截断链(现仅 pop_last_assistant_round),协议级改动独立批)+~~#14 批量审批部分失败提示~~✅(2026-10-05 顺手批:batchApprove 部分失败从静默 console.warn 升级 ai-toast「批量完成:{ok} 项成功,{fail} 项失败」aiTool.batchPartialFail zh/en)+~~#15 ToolCallStarted 竞态~~✅(2026-10-05 顺手批:末条非 assistant 时兜底向前找最近 assistant 落卡,不再丢 running 卡;全程无 assistant 才保持原丢弃)+~~#16 注释与实现矛盾~~✅(2026-10-05 本批:注释纠偏——withConvOp failKey 错误气泡兜底实证(useAiConversations.ts:96),catch 内 console.error 留痕不重复提示)+~~#17 图片上传外部域名~~⏸️(2026-10-05 用户指示延后再论:涉开源边界,业务域名硬编码默认可 df-file-upload-base 覆盖,决策项不动代码)+B-260619-04③销账(实际已落地)。另 R5+V9 消息检索用户指示延后 - [x] 第四批(用户实测反馈 2026-09-29):批量读取文件时工具卡逐卡渲染刷屏 ✅已修——分组默认收起机制 6 月已有但为一次性初始化,流式场景首卡到达即触发完毕,组从 1 卡长到 N 卡不再折叠致 N 张单行卡全展开;改响应式补齐(组达 2 卡阈值即收起)+用户手动操作/审批展开记 touched 不被自动收回 -### 💡 2026-08-04 父子任务支持(✅ 已完成) - -> 目标:完成父任务/子任务的完整支持(数据→后端→前端),UI/UX 重点设计。 -> 详情:[04-功能迭代/父子任务支持设计-2026-08-04.md](./04-功能迭代/父子任务支持设计-2026-08-04.md) - -- [x] **后端** ✅ 父聚合下沉 df-nodes 共享层(`recompute_parent_status`/`advance_task_with_parent`),IPC/AI 工具/MCP 三方统一;df-mcp create_task 支持 parent_id(1 级嵌套校验);delete_task 级联软删子任务(返回 `{ok,cascaded}`) -- [x] **前端** ✅ Tasks.vue 树形列表(折叠箭头+📑图标+子进度徽章 n/m+迷你进度条+缩进竖线圆点);新建弹窗「父任务」下拉(项目锁定);TaskDetail 父面包屑+子任务面板(进度条+快捷推进+添加子任务);i18n zh/en 双份 -- [x] **核查** ✅ cargo check(df-nodes/df-mcp/src-tauri 三 crate) + vue-tsc 全部通过 - -### 💡 2026-06-27 对话上下文透明化(✅ 全部完成) - -> 用户看不到 AI 接收到的上下文信息(目标钉扎/enrichment/system_prompt),导致行为不可理解。 - -- [x] **L1 目标可见** ✅ 对话顶部显示当前 pinned_goals 列表(TopBar.vue 🎯 图标,可查看/清理) -- [x] **L2 Enrichment 可见** ✅ @[项目] 发送前展开 enrichment 摘要(ChatInput.vue enrichment 预览面板,展开/收起/取消关联) -- [x] **L3 完整上下文** ✅ 可展开面板查看 system_prompt / augmentations(TopBar.vue 底部 context 面板) - ### 💡 2026-06-27 统一 Inbox 设计(讨论产出) > 思路:所有需要用户注意的事项(审批/知识待审/灵感确认/其他通知)集中到一个统一入口, @@ -272,40 +267,6 @@ graph TD - ✅ **UX-260617-28 [INFO]** — 已决策:现状够用,不出问题不升级 -### 🔧 2026-06-19 文件拆分升级(核验结论:全部完成) - -> **2026-06-28 核验**:三项 God 文件拆分已全部完成: -> - **tool_registry.rs** ✅ 1091行单函数已拆为按域注册函数 -> - **AiChat.vue** ✅ 4026→**767行**(已抽子组件) -> - **crud.rs** ✅ 2212→拆为 `crud/` 下11个文件 - -**P1 ToolCard 拆分(已核验完成)** - -- [x] **REFACTOR-260619-04 [P1]** ✅ **ToolCard.vue 拆分完成**: - - ToolCard.vue 1527→**373行** - - `ToolResultBody.vue` + `useToolCardHeader.ts`(160行) + `useToolCardRender.ts`(45行) - - `useToolApproval.ts`(审批状态机独立,220行,B-260616-08 loading/挂起时长/toast 完整) - - `useToolCard.ts`(666行,通用工具函数) — 原 ToolCard 组件拆分的目标全部到位 - -**P2 暂缓(窗口未到/收益低)** - -- [ ] **REFACTOR-260619-09 [P2 暂缓]** — **✅事实完成销账(2026-10-04 勘误)**:原 context.rs(1332)已拆为 context/{mod,sanitize,manager_tests}.rs 目录模块,条目过时。**新超标对象=context_helpers.rs(1733 行)**,拆分备勘已出(2026-10-04 [设计文档](./02-架构设计/专项设计/context-helpers拆分备勘-设计-2026-10-04.md),四批迁移+压缩热点殿后+触发条件=上下文管理批稳定后等用户指令) — `crates/df-ai/src/context_helpers.rs` - ---- - -### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并(子代理方案·其他会话实施·P1) - -> 用户需求:AI 多轮 read_file(轮1×2 + 轮2×2)前端一排展开卡片("上面两个下面两个"),希望 UI 合并。后端 convert 已合并连续 tool_result(B-260619-03),前端展示层对齐。子代理分析:同批同类合并**已存在**(ToolCardList groupedToolCalls L92-110,calls>=2 显标题行+默认收起),**痛点是跨轮**(轮1/轮2 各独立 ToolCardList 实例→各成分组)。用户强调"谨慎不出错"。 - -- [x] **B-260619-04 [P1]** ✅ **ToolCard UI 合并方案 A 全部落地**: - - **现状(已查清)**:`ToolCardList.vue` groupedToolCalls 按 tc.name 全局聚合,calls>=2 显「[图标] 读取文件 [N] ▸」标题行 + 默认收起(initDefaultCollapse L139-147)。**同批 read_file×2 应已是折叠组**;用户「下面又两个」=下一轮新 message 的独立 ToolCardList 实例→第二个分组(跨轮未合并)。 - - ✅ **步骤 ① 标题信息增强(已落地)**:groupFileSummary(group) 从 group.calls[].args.path 取前 2-3 个 shortPath 截断显示(“读取 2 个文件 · shortPath1 · shortPath2”),模板已渲染。 - - ⏸️ **步骤 ③ 新轮到达自动收起旧轮**:当前跨轮收起未实现,保留作为低优先增量需求(需在 AiChat.vue watch messages 调 collapseCompletedGroups,风险中)。 - - **不推荐方案 B**(跨轮全局合并,破坏 message 边界,审批/折叠重构高风险)/ **C**(纯视觉框,不解决占用)。 - — `src/components/ToolCardList.vue` - ---- - ### 💡 灵感模块-来源采集/追溯(2026-06-21·待办·新功能范畴) > 继批1-3c 升级后的下一步:灵感来源录入增强 + 自动采集机制。**属新功能范畴**,需独立设计采集规则/触发/去重,下一阶段推进。 @@ -360,26 +321,6 @@ graph TD --- -### 🔍 2026-06-26 灵感模块诊断待办(合并推进·单任务) - -> ✅ 全部修复完成(2026-06-27 核验) - -- [x] **IDEA-FIX-10 [P2🟠]** — **前端 filter + 后端分页漏数据**。团队决策:`hot`/`pending` 保留前端 filter(扩多值属性过度设计);keyword/order_by 已下沉后端。✅ 关闭(2026-08-15:团队决策定案,无后续动作) - ---- - -### 🔍 2026-07-17 aichat 对话功能深度走查待办 - -> 详单:[05-代码审查/aichat-对话功能走查-2026-07-17.md](./05-代码审查/aichat-对话功能走查-2026-07-17.md) - -- [x] **AIC-FIX-17-P0-1** — 流式 watchdog legacy 路径 `convStates.clear()` 全局误杀 → `delete(state.activeConversationId)` ✅ -- [x] **AIC-FIX-17-P0-2** — 审批计时器切/删会话不清 → switch/delete 入口 `clearAllApprovalTimers()` + 注释修正 ✅ -- [x] **AIC-FIX-17-P1-1~11** ✅ 已销账(2026-08-15 核验,1 agent 全量核验 + 主代 4 项抽查源码属实):P1-1/3/4/5(queue/modelOverride/spans/_lastDelta per-conv,F-09 收尾)/ P1-2 drainQueue 互斥 `_drainingConvs` Set(useAiSend:274)/ P1-6 flushCurrentText 守卫 / P1-7 MessageItem 收敛仅 user 角色 / P1-8 _blockCache LRU 300+切会话 resetStreamCache / P1-9 指纹 watch 替代 deep / P1-10 approving 防重入前置(useToolApproval:133)/ P1-11 deleteConversation 相邻回落(useAiConversations:520)。余「MessageList 内联渲染组件化」「缓存主动失效」为优化空间非缺陷 -- [x] **AIC-FIX-17-P2** ✅ 已销账(2026-08-15):7 条中 5 条核验为此前批次已修(ApprovalOverlay 响应式冷却/aiShared 对称收敛/flash watch/maximized 三元/emit 类型),2 条本批落地(tailSeq 注释与实现对齐 useStreamRenderer:142-196、isCompressing 单例补 known limitation 注释 useAiContext:28) -- [x] **AIC-FIX-17-根因** ✅ **2026-08-07 核验销账**(queue/modelOverride/审批/_lastDelta 均已 per-conv) — F-09 per-conv 收尾(queue / modelOverride / _approvalTimers / _lastDelta 单例化) - ---- - ### 🔍 2026-08-01 走查剩余待办(P2/P3/专项·本会话登记) > 本次走查剩余项登记,未实施代码(守 session-role-diagnose-only)。专项已落 memory,此处留指针。 @@ -518,108 +459,6 @@ graph TD **其他已销项**:CSW-P2-7(需实测确认,从待办去除) --- -### 🔍 2026-08-09 TaskDetail 操作按钮缺陷走查(用户实测 + AI 深度审查) - -> 用户反馈"产出部分只有标题空白",触发对 TaskDetail.vue 所有操作按钮的全量走查。覆盖模板/脚本/后端 advance_task / run_workflow 回调链路。共发现 10 项缺陷,按严重度 P0×2 / P1×4 / P2×4 分组。守 session-role-diagnose-only:本会话仅走查+登记,未实施代码。 -> -> **✅ 2026-08-09 全部修复并核验销账**:主代理独立核查(vue-tsc EXIT 0 + diff 语义审)通过。补充说明:P1-4 核验当前代码已满足(advanceChild/setChildPriority 已含 `childMenuId.value = null`),仅销账未改;P1-5 实现位置优化为 watch(taskId)/refresh/handleAdvance 成功三入口调用 `resetWorkflowUi()`(load() 内不加,避免工作流推进中 df-data-changed 触发刷新误清进度面板);P2-10 描述编辑对齐 IdeaDetail 模式(有描述/空描述均可编辑,taskApi.update 落库)。验收标准 1-9 全部满足。 - -**涉及文件**: -- `src/views/TaskDetail.vue`(987 行,主要改动点) -- `src-tauri/src/commands/task.rs`(advance_task,仅参考) -- `src-tauri/src/commands/workflow.rs`(run_workflow 回调,仅参考) -- `crates/df-nodes/src/task_advance_node.rs`(advance_task_atomic,仅参考) - ---- - -## 🔴 P0 缺陷(2 项,必须修) - -### P0-1 双重推进竞态:工作流推进中手动推进按钮仍可用 -- **位置**:TaskDetail.vue:48 vs :55 -- **现状**:工作流按钮 `:disabled="wfAdvancing || advancing"`(检查两者),手动按钮 `:disabled="advancing"`(只检查 advancing) -- **问题**:`wfAdvancing=true` 时手动按钮仍可点 → 同时发起 `run_workflow`(回调 advance)+ 直调 `advance_task` 两个推进请求 → 第一个成功改 status,第二个 CAS 失败返回 Err → 前端显示红色错误,用户困惑"明明成功了为什么报错" -- **修复**:手动推进按钮 `:disabled` 加 `|| wfAdvancing`(1 行) - -### P0-2 工作流推进与手动推进按钮语义重复(todo 状态双"开始") -- **位置**:TaskDetail.vue:45-57 -- **现状**:todo 状态同时显示「工作流推进」+「开始」两个按钮,都推进到 in_progress -- **问题**:用户不知道该点哪个;设计意图是工作流推进会跑 AiNode 生成产出,手动"开始"只是纯改状态,但按钮文案没有区分 -- **修复**:工作流推进按钮文案改为「AI 执行推进」或「工作流推进(含 AI 产出)」 - ---- - -## 🟠 P1 缺陷(4 项) - -### P1-3 子任务快捷菜单不校验状态机合法性 -- **位置**:TaskDetail.vue:286-296 -- **现状**:`quickStatuses` 硬编码列出全部 6 态 [todo, in_progress, in_review, testing, done, blocked],不按子任务当前 status 过滤 -- **问题**:todo 状态子任务的菜单显示 testing/done/in_review 等非法目标,点下去后端 can_transition 拒绝 → 红色错误 -- **对比**:顶部手动推进按钮用 `ADVANCE_MAP` 严格按状态机过滤,子任务菜单却无此逻辑 -- **修复**:子任务菜单复用 `ADVANCE_MAP[child.status]` 过滤合法目标 - -### P1-4 子任务操作后快捷菜单不关闭 -- **位置**:TaskDetail.vue:326 advanceChild / :337 setChildPriority -- **现状**:操作完成后不 `childMenuId.value = null` -- **问题**:点了"推进到 done"或"改优先级"后菜单仍挂着悬浮 -- **修复**:两函数操作完成后 `childMenuId.value = null`(2 处) - -### P1-5 refresh/切任务后工作流进度/DAG 残留 -- **位置**:TaskDetail.vue:619 load() 函数 -- **现状**:load() 只重置 task/projects/ideas,不清 `wfExecId` / `wfDagJson` / `wfResult` -- **问题**:切任务或点 refresh 后右栏仍显示旧任务的工作流进度和 DAG -- **修复**:load() 开头加 `wfExecId.value = null; wfDagJson.value = ''; wfResult.value = null`(3 行) - -### P1-6 手动推进后工作流进度面板不清空 -- **位置**:TaskDetail.vue:470 handleAdvance -- **现状**:成功后只 `task.value = updated`,不清工作流相关 ref -- **问题**:手动推进后右栏仍显示旧工作流进度 -- **修复**:handleAdvance 成功后清空 wfExecId/wfDagJson/wfResult - ---- - -## 🟡 P2 缺陷(4 项) - -### P2-7 子任务创建无长度/重名校验 -- **位置**:TaskDetail.vue:365-381 confirmSubtask -- **现状**:标题只 `trim()` 判空,不校验长度上限/重名;成功路径不清 `errorMsg.value` -- **修复**:加长度上限(如 128 字)、同父重名校验、成功路径清 errorMsg - -### P2-8 wfResult timer 生命周期不严谨 -- **位置**:TaskDetail.vue:424 watch 回调 -- **现状**:工作流完成后 setTimeout 3s 清 wfResult;切任务时 timer 仍在但 wfExecId 已清 -- **影响**:低风险(最多多等 3s),但 timer 引用管理不严谨 -- **修复**:load() 重置时清 `_wfResultTimer` - -### P2-9 子任务页无"是子任务"提示 -- **位置**:TaskDetail.vue:108(父面包屑)+ :133(子任务面板仅顶层显示) -- **现状**:子任务详情页结构与顶层任务几乎一样,只有顶部多一行父面包屑 -- **修复**:加一行提示「该任务为子任务,隶属于:{parentTitle}」 - -### P2-10 空描述无编辑入口 -- **位置**:TaskDetail.vue:68 `
` -- **现状**:无描述时描述面板整块消失,无"添加描述"入口 -- **修复**:无描述时显示空态 + "添加描述"入口 - ---- - -## 验收标准 - -1. 工作流推进中手动推进按钮禁用,无双重推进竞态 -2. todo 状态两个按钮文案可区分(工作流推进 vs 纯手动开始) -3. 子任务快捷菜单只显示状态机合法目标 -4. 子任务操作后菜单自动关闭 -5. 切任务/refresh/手动推进后右栏工作流进度正确重置 -6. 子任务创建有长度/重名校验,错误提示清晰 -7. 子任务页有"是子任务"明确提示 -8. 空描述有编辑入口 -9. vue-tsc EXIT 0 - ---- - -**关联**:父任务「总览页 + 项目列表 UI/UX 优化(持续更新)」(id: 0115aea6-b423-482a-8a64-b793a66a0417)—— 本走查作为该任务的子任务延伸。 - ---- - ### 🔍 2026-08-09 项目详情页走查(概览/文件/依赖图 · 3 agent 并行 + 主代核查) > 用户要求「检查项目详情(概览、文件、依赖图)存在的问题」并「先检查做记录,不直接改」。详单见 [项目详情页走查-2026-08-09.md](./05-代码审查/项目详情页走查-2026-08-09.md)。共 **60 项**(P0×1 / P1×6 / P2×14 / P3×27)。守 session-role-diagnose-only:仅走查登记,未实施代码。 @@ -731,9 +570,9 @@ graph TD **P3(25 项)**: 见详单文档(MCP-7~16 / EX-1~4 / PRJ-2~4 / TUN-2~4 / RLY-2~5) > **全系统走查(阶段②)完成 ✅**——前端 13 模块 + 后端 9 命令 + 全部 crate 已走查登记。进入**阶段③批量打包推进 /advance**。 -### 🔴 2026-08-09 AI 自审/工作流推进链路缺陷(task_id 断裂 + AiNode 缺 prompt + 审批无 UI) +### 🔴 2026-08-09 AI 自审/工作流推进链路缺陷(✅ P0×3 已修,2026-10-05 核验销账) -> 用户「检查 AI self-review 遇到的问题」触发对工作流推进链路的深度审查。**核心结论:工作流推进按钮从 TaskDetail 触发时,三条推进链(in_progress/testing/done)全部不可用**。守 session-role-diagnose-only:本会话仅走查+登记,未实施代码。 +> 用户「检查 AI self-review 遇到的问题」触发对工作流推进链路的深度审查。**原结论「三条推进链全部不可用」已过时——P0×3 全部已修**(task_id 注入 workflow.rs:340 + AiNode 自审落库含 agent 模式 + TaskDetail 审批 UI 随 2eea011 审批重设计批落地),核验详录迁 [07-项目管理/todo归档/2026-10-05.md](./07-项目管理/todo归档/2026-10-05.md)。以下原始诊断保留作档案,修复状态见归档勘误。 **涉及文件**: - `src-tauri/src/commands/workflow.rs`(run_workflow_inner 核心) @@ -833,7 +672,7 @@ graph TD > 来源:中断根治批次全量走查(正常设计路径无漏 emit,风险集中四类)。P1-2/P0-1/P2-1 本批已修,以下为登记项。 -### P0 run_agentic_loop 包 catch_unwind +### P0 run_agentic_loop 包 catch_unwind ✅已修(2026-10-05 核验:loop 主体 unwind_to_result 包裹,panic 转 teardown_round 补终态,5 单测锁定 mod.rs:3135-3165) - **位置**:`agentic/mod.rs` run_agentic_loop 主体(无 catch_unwind) - **现状**:任一轮 prepare/stream/finalize/tools 段 panic → task 死亡 → GeneratingGuard::drop 只迁 Idle+emit AiConvStateChanged,**不 emit AiError/AiCompleted**,heartbeat 随作用域死亡——「工具完成后流断+无终止事件」的精确根因形状(前端探测式续跑已兜住后果,此为后端根治)。 - **修复**:整体包 `std::panic::catch_unwind(AssertUnwindSafe(...))`,unwind 时 emit AiError{error_type:Internal} 再收敛;guard Drop 兜底保留。核心 loop 改动,需谨慎设计+场景回放测试。 @@ -871,11 +710,11 @@ graph TD - [x] P2 看门狗 legacy/per-conv 双轨三错配:useAiSend.ts:169/:241/:512 统一迁移传 convId + `_resumeAttempts` 收尾清(债务本就要还) ✅已修(2026-10-04 核验:regenerate(:183)/editMessage(:259)/stopChat(:512+)三处均已传 per-conv convId 重置/清理看门狗,stopChat 收尾 clearResumeAttempts(activeConversationId),注释明确记载「不再落 legacy 单 timer fallback」) - [x] P2 _lastDeltas 双路径漏清:useAiEvents.ts:96-115/:154-161 补 clearLastDelta ✅已修(2026-10-04 核验:后台终止分支(:217)与停靠卡归集分支(:131)均补 clearLastDelta,前台收尾 cleanupTerminatedConversation 亦有显式清理,注释记载防残留记录误丢下轮同内容 delta) - [x] P2 removeGoal 不回写 store/不回滚:TopBar.vue:530-539 对齐 withConvOp 模式 ✅已修(2026-10-04 核验:removeGoal 已回写 `conv.pinned_goals = next`(注释 UX 走查-对话域 B4),防 loadGoals 重读旧列表复活已删目标) -- [ ] P2 forceConverge 收敛/错误气泡缺 activeId 守卫(useAiStream.ts:115-121,与本批 onStreamTimeout 修复同家族) +- [x] P2 forceConverge 收敛/错误气泡缺 activeId 守卫 ✅已修核验销账(2026-10-05):per-conv 触发守卫已在 forceConverge 入口(会话已删除或已收敛即清态早退 useAiStream.ts:96-103)+补气泡双过滤(仅 `!convId || convId === activeConversationId` 才 push,:138-139,后台会话静默收敛气泡不错位) ### 机械批(可独立小批) -- [ ] remote_bridge 6 处 + agentic/mod.rs 4 处裸 emit 换 emit_ai_chat_event(miniapp 收不到错误/心跳/熔断卡) -- [ ] i18n searchFilesBlocked 死键裸 @(zh-CN/en aiChat.ts:198/197)转义或删 +- [x] remote_bridge 6 处 + agentic/mod.rs 4 处裸 emit 换 emit_ai_chat_event ✅已收核验销账(2026-10-05):grep 全库 `\.emit\("ai-chat-event"` 仅剩正典 event_bus.rs:87 本体+注释+有意分路的 completed_once(mod.rs:2588 注释保护),remote_bridge 已全走 publish_event/emit_ai_chat_event 双写正典——DRY-B #1 批次顺带收口,文档滞后案 +- [x] i18n searchFilesBlocked 死键裸 @ ✅已修(2026-10-04 狗粮实证批⑤:zh-CN/en aiChat.ts 裸 @ 改 @ 转义,文档滞后案,2026-10-05 复核确认) ### 登记观察(定级前先核) - [x] model_fetch 空列表 Ok([]) 上层写回清空模型池 ✅已修(2026-10-03 N1):provider.rs 空守卫跳过写回+前端 toastFetchEmpty 同语义守卫 @@ -886,35 +725,36 @@ graph TD > 前端 13 项(P1×2/P2×4/P3×7) + 后端 P1×1/P2×3/P3×8;后端报告含 6 点「确认无恙」防重查。 -- [ ] P1 分离窗下 TaskDrawer 关联会话死点击:openConversation detached 分支无跨窗信号不聚焦(useAiConversations.ts:661) -- [ ] P1 子代理运行期切回父会话 spawn 卡「已完成」幽灵态:回载 parseConvMessages 恒标 completed+运行期零事件(useAiConversations.ts:230) -- [ ] P1 后端 外层 960s 超时 drop 子循环 future→统一出口全跳过(报告蒸发+注册表泄漏+子会话滞留):deadline 检查下沉 stream chunk 循环或 select 超时分支特殊化补 AiAgentFinished(truncated)(audit/mod.rs:306) -- [ ] P2 rejected_tools 前端丢弃不可扫描:完成卡收字段+warn 色 chip(useAiEvents.ts:70) -- [ ] P2 完成卡纯内存重启蒸发:切换/挂载按 origin='sub_agent' 回补(aiShared.ts:630) -- [ ] P2 完成卡恒贴底时序漂移:按完成轮次桶插入(MessageList.vue:1080) -- [ ] P2 轨迹浮层无身份标识:头补 res.title/任务书首行(MessageList.vue:1584) -- [ ] P2 后端 白名单执行期不拦 Low 工具:allowed 随子循环传入 process_tool_calls(audit/mod.rs:814) -- [ ] P2 后端 父删除级联孤儿轨迹照落库:cascade_stop_children_locked 删除形态补 deleted_convs.insert(sub_agent.rs:194) -- [ ] P2 后端 S4 反查 LIMIT 1000 先截断后过滤:匹配条件下沉 SQL 或辅助索引(audit/record.rs:315) -- [ ] P3×15(前端7+后端8):deny envelope 人话渲染/浮层 Esc/spawn_agent 本地化/卡 loading/停止标「已取消」/后台完成通知/已删会话静默新建 + 并发配额挤兑/事件广播噪音/流后取消补 envelope/bypass 连带解除黑名单/落库元数据错位/peek 校验/空收敛降级/AiAgentStarted——见两报告 P3 节 +- [x] P1 分离窗下 TaskDrawer 关联会话死点击:openConversation detached 分支无跨窗信号不聚焦(useAiConversations.ts:661) ✅已修核验销账(2026-10-05):openConversation detached 分支已 emitDetachedOpenConversation 跨窗信号+分离窗 handleDetachedOpenConversation 承接切换+not_found toast 落承接窗,注释明确记载 UX走查 P1-1 +- [ ] P1 子代理运行期切回父会话 spawn 卡「已完成」幽灵态:回载 parseConvMessages 恒标 completed+运行期零事件(useAiConversations.ts:230) ——半修(2026-10-05 核验):回载态判定已修(toolCalls status 改 `toolResultMap.has || !convGenerating ? completed : running`,运行期回载不再恒 completed);剩「运行期零事件」半(子代理运行事件仍不推父会话视图)待批 +- [x] P1 后端 外层 960s 超时 drop 子循环 future→统一出口全跳过(报告蒸发+注册表泄漏+子会话滞留):deadline 检查下沉 stream chunk 循环或 select 超时分支特殊化补 AiAgentFinished(truncated)(audit/mod.rs:306) ✅已修(2026-10-05 核验:audit/mod.rs:326-329 子代理外层 deadline=SUB_AGENT_WALL_TIMEOUT_SECS+60 缓冲,循环内以截断报告优雅收尾非硬腰斩,「报告蒸发」注释原文在案) +- [x] P2 rejected_tools 前端丢弃不可扫描:完成卡收字段+warn 色 chip(useAiEvents.ts:70) ✅已修(2026-10-05 核验:aiShared.ts:630 rejected_tools 字段+MessageList:1453 warn chip「工具名×次数」+title 全清单) +- [x] P2 完成卡纯内存重启蒸发:切换/挂载按 origin='sub_agent' 回补(aiShared.ts:630) ✅已修(2026-10-05 核验:aiShared.ts:677 ai_sub_agent_list 反查重建,轨迹存在即视为 completed) +- [x] P2 完成卡恒贴底时序漂移:按完成轮次桶插入(MessageList.vue:1080) ✅已修(2026-10-05 核验:renderItems 内按 finishedAt 落桶插桩,「不恒贴消息流最底部」注释在案) +- [x] P2 轨迹浮层无身份标识:头补 res.title/任务书首行(MessageList.vue:1584) ✅已修(2026-10-05 核验:agentPeekSubject{agent,convTitle,task,taskFull} 四字段+任务行取子会话首条 user 消息首行) +- [x] P2 后端 白名单执行期不拦 Low 工具:allowed 随子循环传入 process_tool_calls(audit/mod.rs:814) ✅已修(2026-10-05 核验:audit/mod.rs:217 P2-1 执行期白名单判定纯函数+whitelist-clamp 拒绝语义「spawn 时被裁剪或不存在」人话提示) +- [x] P2 后端 父删除级联孤儿轨迹照落库 ✅已实施核验销账(2026-10-05):CascadeStopForm::Delete 形态已落地(sub_agent.rs:268 cascade_stop_children_locked,行282 deleted_convs.insert 同锁补标记,与出口 alive 判定互斥注释在案;出口 sub_agent.rs:551-555 见标记即放弃落库防空壳复活)——文档滞后案 +- [x] P2 后端 S4 反查 LIMIT 1000 先截断后过滤:匹配条件下沉 SQL 或辅助索引(audit/record.rs:315) ✅已修(2026-10-05 核验:P2-3 task_id 预筛下沉 SQL——result LIKE 参数化预筛缩小窗口,精确判等仍在 Rust 侧 match_task_audit_row,「禁裸 LIKE 判等」不破,预筛只多放不漏放+单测锁定) +- [x] P3×15(前端7+后端8):✅(2026-10-05 销账核验:15 条实际全部已落码或已缓批登记——deny envelope 人话渲染(parseDenyEnvelope+deniedTool i18n)/浮层 Esc(closeAgentTraceOnEsc+切会话 closeAgentTrace)/spawn_agent 本地化(spawnAgentPrefix zh/en)/卡 loading(agentPeekLoadingId spinner)/停止标「已取消」(stop_kind 结构化+fallback 启发式)/后台完成通知(agentFinishedToast)/已删会话静默新建(uf1 noCreateFallback)/空收敛降级(dx1 empty_convergence)/流后取消补 envelope(P3-3)/bypass 连带解除黑名单(P3-4 PathBypass 三态分流)/落库元数据错位(P3-5 SaveMetaOverride)/peek 校验(agentFinNoTrace 禁点+title)/事件广播噪音(P3-2 文档补全确认)——todo 行未勾属滞后;唯 AiAgentStarted 缓批「子代理运行面二期」+并发配额挤兑缓批已立项)——见两报告 P3 节 # 子代理深查两路(2026-10-03 晚,详情 temp/深查-子代理{AI效果面,x上下文管理交叉面}-2026-10-03.md) > 与上节 25 项零重叠(报告尾附排除对账)。修复批:四路走查修复收口后,确定性项先行,需拍板项进拍板集。 -- [ ] P1[确定性] 子代理指引真空:父会话实测蒸馏的四段工具纪律(read_symbol 优先/URL 三工具纪律/重复调用检测/get_app_config)未注入子代理 system prompt,而白名单缺省下发 25+ Low 工具,主会话已根治的翻车模式(一次死磕烧 1/3 预算)会在子代理原样复发;修法=抽共享指引段同源拼接 +- [x] P1[确定性] 子代理指引真空:父会话实测蒸馏的四段工具纪律(read_symbol 优先/URL 三工具纪律/重复调用检测/get_app_config)未注入子代理 system prompt,而白名单缺省下发 25+ Low 工具,主会话已根治的翻车模式(一次死磕烧 1/3 预算)会在子代理原样复发;修法=抽共享指引段同源拼接 ✅已修(2026-10-05 核验:prompt.rs sub_agent_tool_discipline_section 同源拼接,sub_agent_system_prompt 内引用,obscura 交互条款已在共享函数内裁剪) - [ ] P1[需拍板] 熔断无收尾轮:max_rounds 打满是常态,截断时父拿到「末轮思考独白」而非报告;修法=熔断触发补一次无工具收尾 LLM 调用(涉预算策略) - [ ] P1[需拍板] 报告被视图压缩吞掉:summarize_tool_results 无新近度豁免,报告 envelope>2KB 下一轮即被 JSON 分支折叠(512 字符),父 LLM 从未见过报告全文而 DB/前端卡是全文,追问细节答不上;涉上下文策略(stall_detector.rs:278-350/context_helpers.rs:461-513) -- [ ] P2 max_tokens=8192 截断零信号:finish_reason=length 被通道层按正常完成吞,报告腰斩仍标 completed[确定性可修] -- [ ] P2 任务书=首条 user 消息,超预算时第一个被逐出且子代理无目标钉扎(主循环有,子循环显式传空 sub_agent.rs:594)[确定性可修] -- [ ] P2 预算三值(轮次/token/900s)不告知子代理,系统提示词硬编码「默认最多 6 轮」[确定性可修,文案] -- [ ] P2 Fatal 错误细节被吞(RoundOutcome::Fatal 无 payload),父拿无因报告无法针对性重派[中小扩参] +- [x] P2 max_tokens=8192 截断零信号:finish_reason=length 被通道层按正常完成吞,报告腰斩仍标 completed[确定性可修] ✅已修(2026-10-05 核验:RoundOutcome::Continue 携 truncated_by_max_tokens 全链穿透,收敛时追加「⚠ 回复因 max_tokens 上限被截断」系统提示 mod.rs:1915-1925) +- [x] P2 空回复静默假成功(子代理侧) ✅已修(2026-10-05 核验:sub_agent.rs:384-397 末轮空白降级 Truncated+stop_kind=empty_convergence,不再 Completed+空报告失真;主循环侧同语义归无人值守 P2 批) +- [x] P2 任务书=首条 user 消息,超预算时第一个被逐出且子代理无目标钉扎(主循环有,子循环显式传空 sub_agent.rs:594)[确定性可修] ✅已修(2026-10-05 核验:P2-2 任务书钉扎—— 全文并入 system prompt「上下文裁剪不可逐出」段) +- [x] P2 预算三值(轮次/token/900s)不告知子代理,系统提示词硬编码「默认最多 6 轮」[确定性可修,文案] ✅已修(2026-10-05 核验:sub_agent_system_prompt(max_rounds) 动态注入三值实际生效值+防回归断言禁「默认最多 6 轮」文案回潮) +- [x] P2 Fatal 错误细节被吞(RoundOutcome::Fatal 无 payload),父拿无因报告无法针对性重派[中小扩参] ✅已修(2026-10-05 核验:sub_agent.rs:897 P2-4 Fatal 携最后诊断文本进出口报告,封顶防超长污染) - [ ] P2 namespace 10MB 全局 LRU 淘汰:报告蒸发为「重新调用」死路+多代理共池互踩 ✅设计完成(2026-10-04 [设计文档](./02-架构设计/专项设计/namespace淘汰治理-设计-2026-10-04.md)):pin 机制+EvictionLog 富提示(替代裸 EVICTED_PLACEHOLDER)+owner 软隔离三招,零 schema 变更;2 拍板点(pin 上限保护/报告外默认 pin 范围),约 0.5-1 天 - [ ] P2 被拒事实父上下文仅报告尾段单副本,LLM 压缩 prompt 无保留指令[需拍板] - [ ] P2 完成卡(前端全文)与父上下文(压缩)双轨无「已消化」降级机制 ✅设计完成(2026-10-04 [设计文档](./02-架构设计/专项设计/子代理完成卡已消化降级-设计-2026-10-04.md)):勘察修正——重启回补已存在(aiShared.ts:677 ai_sub_agent_list 反查重建),设计聚焦 digested 标记(KV 持久)+卡降级交互+ai_sub_agent_trace 只读工具(LLM 追问自助通道,兼为 namespace 蒸发兜底);2 拍板点,约 1-1.5 天 - [ ] P2 子会话缺 maybe_auto_compress 层+预算恒 128k 默认[需拍板] - [ ] P3 spawn task 全文永续无瘦身/8KB 截断文案对子代理语境失真/环境画像日期不注入/报告 8K 头尾截断恰切证据区/temperature 0.7 共享/model 回退不回报/namespace 挤兑/一次性语义未向父声明/报告约定缺「文件路径原样保留」/语言无 lang 参数——见两报告 P3 节 -- [ ] 观察 uf2 修复引入:取消判定 summary.includes('已被取消') 中文子串启发式,根治=后端透出结构化 stop_reason/取消标志字段 +- [x] ~~观察 uf2 修复引入:取消判定 summary.includes('已被取消') 中文子串启发式,根治=后端透出结构化 stop_reason/取消标志字段~~ ✅已根治核验销账(2026-10-05 本批,文档滞后案):结构化字段已全链落地——后端 SubAgentStopKind::Cancelled 两处取消检查点同点产出(sub_agent.rs:716/824)+ AiAgentFinished.stop_kind 事件透出(serde 单测锁定)+ 前端 agentFinStatusText 结构化优先(MessageList.vue:1127,cancelled/max_rounds/wall_clock/token_budget 细分直达语义);中文子串判定降级为旧后端兼容性回退(无 stop_kind 字段才走),防御性保留非主路径 # 无人值守长任务(2026-10-04 用户拍板方向,设计先行) @@ -923,7 +763,7 @@ graph TD > 缺陷靶心分析:temp/深查-无人值守稳定性缺陷-2026-10-04.md(P1×5/P2×7+效率 5+观测可控 5,接驳点已标注)。 - [ ] 设计评审后按分期实施(P0 边界模型+旁路主干 → P1 自愈+通知 → P2 断点续跑+信任分级) -- [ ] P1 缺陷随分期消化:~~①审批超时取消后循环永不恢复~~ ✅①已修(2026-10-04 核验:cancel_expired_approval 第6步清幽灵态迁回 Idle+第7步安全停靠 AiTaskParked+NeedAction 通知,audit/mod.rs:530-555) ②spawn_agent 注册 Medium 无人值守动线停摆(归 P0 旁路) ③MidStream 断流=整任务终止(入打断点表,P1) ④轮级重试 30s 总预算全候选共享无跨轮重入(P1,任务级重入;工作区已见 ROUND_REENTRY_MAX 跨轮重入层在飞实施,待合入核验) ⑤重启后任务静默失踪(归 P2 断点续跑) +- [ ] P1 缺陷随分期消化:~~①审批超时取消后循环永不恢复~~ ✅①已修(2026-10-04 核验:cancel_expired_approval 第6步清幽灵态迁回 Idle+第7步安全停靠 AiTaskParked+NeedAction 通知,audit/mod.rs:530-555) ②spawn_agent 注册 Medium 无人值守动线停摆(归 P0 旁路) ③~~MidStream 断流=整任务终止~~ ✅已修(2026-10-05 核验:RoundOutcome::MidStreamSaved 分支 emit_task_parked 安全停靠+NeedAction 托盘通知「midstream」原因,mod.rs:1146) ④~~轮级重试 30s 总预算全候选共享无跨轮重入~~ ✅已修(2026-10-05 核验:任务级跨轮重入已落地——ROUND_REENTRY_MAX=3 指数退避 60/300/900s,Continue 成功连续失败归零,预算耗尽转停靠 mod.rs:1071/1124/1162) ⑤重启后任务静默失踪(归 P2 断点续跑) - [ ] P2 缺陷登记:panic 收尾伪装正常完成无通知/context_length Fatal 无降级/空回复静默假成功/前端 3 次探测强制收敛与后端分裂/checkpoint 审计只增不清 - [ ] 效率面:模型无轮内分级/警示消息打断前缀缓存/换参同语义死磕不计数/子代理串行 await/token 花费中途不可查/纠偏只能排队不能注入/预算中途调整不可达(归设计补遗两章分期) @@ -947,21 +787,38 @@ graph TD - [x] P0 git.rs:161 字节切片撞 UTF-8 边界 panic(中文 diff 必炸,13:44 两次实证) ✅已修(分析当场,回退字符边界) - [x] P1 Temperature 原样透传不支持的模型(sensenova 等仅允许 1,9 次 400 实证)——按模型适配/省略 ✅已修(2026-10-04:ModelConfig 加 temperature_supported 字段+presets 标 sensenova-6.7-flash-lite=false+启发式 sensenova 前缀判定;openai/anthropic 双协议转换层不支持即省略字段;df-ai 560+df-ai-core 33 测试全绿含新增锁行为测试) -- [ ] P1 压缩无 memoization:view-only 压缩 2280 次中 369 组同内容反复压(最坏 54 次),prompt 1.2 亿 tokens=completion 115 倍——同内容哈希跳过 -- [ ] P1 标题采到 assistant 首句('信息已足够。让我看一眼...'/'
Web Interface Guidelines 视角(子代理全量走查+主代复核)。前四轮零重复:本轮全是「键盘/读屏/触屏可达性」层,与历轮「交互逻辑」层正交。 +> 计数:P2 × 4 / P3 × 9(P1 零)。 + +- [x] P2-M1 MessageItem 三操作按钮 hover-only:键盘聚焦不可见+触屏不可达(MessageItem.vue:129-136,217-219)——补 :focus-within 显形+aria-label,一处 CSS ✅已修(2026-10-05:.ai-msg-user:focus-within 两选择器入组+编辑/复制按钮补 aria-label) +- [x] P2-T1 TopBar 四面板徽章 span@click 键盘不可达(TopBar.vue:138 等,面板行为层已完备仅入口缺失)——改 button 或补键盘三件套 ✅已修(2026-10-05:四徽章 span→button type=button+.ai-tool-badge 归零按钮默认样式+focus-visible 描边) +- [x] P2-T2/T3 TopBar 约 8 处交互 div/span(@click 无键盘处理):provider 切换(:30)/审批徽章(:18)等——统一改 button ✅已修(2026-10-05:审批徽章 div→button+provider-bar div→button,两处 CSS 归零+focus-visible;余 model select 原生可聚焦,more 菜单已是 button) +- [x] P2-G1 流式输出无 aria-live:读屏用户对 AI 回复零感知(MessageList.vue 全文零命中,唯一例外 ApprovalOverlay role=alert)——容器加 role="log" aria-live="polite" ✅已修(2026-10-05:.ai-messages 容器加 role=log+aria-live=polite) +- [x] P3-M2/G4 按钮仅 title 无 aria-label(MessageItem:129-137/ChatInput 仅 :53 一处) ✅已核验(2026-10-05:MessageItem 编辑/复制按钮 aria-label+svg aria-hidden 已齐,残留 ChatInput :53 一处,下批顺带) +- [x] P3-G2 装饰性图标缺 aria-hidden ✅已修(2026-10-05 机械批:ps1 正则 `(?s)]*aria-hidden)` 批量插入 193 处(22 文件含多行开标签;前置核验全库零 role=img//aria-label 语义型 svg;已有 10 处负向前瞻跳过),vue-tsc EXIT 0) +- [x] P3-G3 滚动容器/弹层无 overscroll-behavior ✅核验销账(2026-10-05):桌面 Tauri WebView2 无移动端橡皮筋回弹/滚动链穿透问题,现状即合理,不引入 +- [x] P3-G5 MessageList user-select:none 9 处 ✅核验销账(2026-10-05):逐处核对 9 处全为交互控件(bubble-toggle/time-sep/归档段折叠条/trace 折叠头/think-pill/步骤行/retry chip/agent-fin 完成卡头),零误伤正文选择,现状即合理 +- 好评:ConversationSidebar/TaskBoundaryCard/DirAuthDialog 经前四轮修复质量扎实;无 transition:all/autoFocus 滥用等反模式 + # ai-chat UX 走查第四轮(2026-10-04,增量深查,详情 temp/UX走查-aiChat缺陷-2026-10-04.md) + + > P1 零;P2×7/P3×11;已修三轮清单已硬排除。修复批开工以现场源码为准。 - [x] P2-A2 边界配置卡目录白名单零校验 ✅已修(词法校验+就近红字),坏路径拖到运行期才暴露(TaskBoundaryCard.vue:163) @@ -972,7 +829,7 @@ graph TD - [x] P2-B2 纯发图+非视觉模型 ✅已修(前后端双闸拒纯图),占位串当用户消息喂模型跑偏一轮(chat.rs:789) - [x] P2-B3 error_type 大面积 Unknown ✅已修(classify_permanent→ProviderConfig CTA),永久错误也显「重试」必再败(MessageList.vue:688+mod.rs:596) - [x] 回归疑点 App.vue:227 全局快捷键 ✅已修(双文件 target 守卫+AIChat:339)(Ctrl+I/N/R/L/K/B)无 target 守卫,仅 Esc 有(走查复核提请,修时验证) -- [ ] P3×11(文案四条已顺带修 ✅:fatal 矛盾措辞/cancelled 续跑 CTA/high 裸串/TopBar ellipsis;余 9 布局+虚拟滚动+压缩面包屑待批) +- [x] P3×11(文案四条已顺带修 ✅:fatal 矛盾措辞/cancelled 续跑 CTA/high 裸串/TopBar ellipsis)+~~A5 停靠卡 scrollIntoView 无效~~✅(2026-10-05 本批:删 no-op 调用+cardRef/nextTick/watch 残留引用清理——卡挂输入区上方常驻可见非消息流,原注释意图错位)+~~A9 TopBar 进度徽章前 4 轮盲区~~✅(2026-10-05 本批:mod.rs 进度 emit 条件加 `|| iteration == start_iteration`——首轮即发快照,起步阶段「在干活」信号不再缺席,托盘场景唯一进度面补齐)+余 7 项本批全清(2026-10-05):~~A8 双进度矛盾~~✅(TopBar 快照徽章在输入区 agentic 实时进度可见时隐藏——`v-if="taskProgress && !agenticProgressVisible"`,同口径 computed 对齐 AiChat showAgenticProgress;快照徽章只服务托盘/静默期场景)+~~A10 边界盾标跨窗空洞~~✅(ConversationSidebar onMounted 补拉 hydrateBoundaryBadges——对列表全部会话拉边界镜像,已有条目跳过省 IPC,治分离窗授权/重启后盾标全消失)+~~A11 切会话丢边界编辑~~✅(TaskBoundaryCard hasDirtyEdits computed 比对表单 vs 镜像,脏编辑时切会话不静默重填保留编辑+琥珀提示条 dirtyKept)+~~A12 预算校验不定位+无防重~~✅(parseBudget 记 budgetErrField 错误字段,红字着色具体输入框+toast 指名 budgetInvalidField;confirm/clear 加 confirming in-flight 禁用防双击双 IPC)+~~A13 echoSummary high 裸串~~✅(前批已修:riskHigh 分支复用 agentTrace.riskHigh)+~~B4 压缩静默~~✅(useAiContext autoCompressedFlash ref:AiAutoCompressed 置 true 4s 自灭,AiChat 输入区上方 hint 行显示「上下文已自动压缩」aiChat.autoCompressed zh/en——不打断 LLM 流不弹 toast)+~~B5 惰性渲染评估~~✅(评估后最小实施:MessageList collapsedPreviewHtml——折叠态长消息(>1500字)仅渲染前 800 字符预览,全量 markdown 渲染延迟到展开瞬间,治长会话切换/挂载成本线性涨;虚拟滚动回滚维持不做) # 后续规划登记(2026-10-04 用户口述) @@ -982,11 +839,11 @@ graph TD > 当日 ~810 次工具调用;主循环配置 max_iterations=0(不限)实证 225 轮零触顶;子代理 spawn max_rounds 6 默认/模型自给 10→20(上限 20)。 -- [ ] P1 工具纪律失守:read_file 本支持 offset/limit 分页+search(工具描述明示)但模型不用,67+ 次 run_command PowerShell/python 切片绕道读文件——大文件 read_file 自动截断返回并带「用 offset/limit」引导+系统提示词禁 run_command 读文件 -- [ ] P1 patch_file old_text 未命中 28%(38/136,全部「未找到目标文本」):加空白归一化回退匹配(唯一命中自动采用并提示),工具描述教「3-5 行锚点小步 patch」 -- [ ] P2 子代理默认 max_rounds=6 偏小,父试探性提到 10/20:默认提到 12 或按任务书长度给建议值 -- [ ] P2 验证烧轮:全量 cargo check 多次(单次数十秒),引导 check -p 单包/单文件快检策略 -- [ ] P3 并行子代理轮次徽标无身份,交错显示「第3轮/第2轮」致乱序观感(机制=各自独立计轮,正常):徽标带 agent 序号/任务首词 +- [x] P1 工具纪律失守:read_file 本支持 offset/limit 分页+search(工具描述明示)但模型不用,67+ 次 run_command PowerShell/python 切片绕道读文件——大文件 read_file 自动截断返回并带「用 offset/limit」引导+系统提示词禁 run_command 读文件 ✅已修(狗粮批 a6a49ce8:prompt.rs file_reading_guidance 中/英双版,父 system_prompt 两语言+子代理纪律段同源拼接) +- [x] P1 patch_file old_text 未命中 28%(38/136,全部「未找到目标文本」):加空白归一化回退匹配(唯一命中自动采用并提示),工具描述教「3-5 行锚点小步 patch」 ✅已修(在飞批:old_text 精确未中自动空白归一化重试,唯一命中整行区间替换+warning 注明,276 行含测试) +- [x] P2 子代理默认 max_rounds=6 偏小,父试探性提到 10/20:默认提到 12 或按任务书长度给建议值 ✅已修(2026-10-05:SUB_AGENT_DEFAULT_MAX_ROUNDS=12+schema/greeting 文案三处同步,spawn_agent 3 测+sub_agent 19 测全绿) +- [x] P2 验证烧轮:全量 cargo check 多次(单次数十秒),引导 check -p 单包/单文件快检策略 ✅已修(2026-10-05 本批:prompt.rs verification_fast_check_guidance 中/英——窄域验证优先(单包 check -p/单文件回读),全量留阶段收官;父会话 en/zh 模板+子代理工具纪律段三处同源拼接) +- [x] P3 并行子代理轮次徽标无身份,交错显示「第3轮/第2轮」致乱序观感(机制=各自独立计轮,正常) ✅已实施核验销账(2026-10-05 本批,文档滞后案):A-3 序号身份已落地——agentFinSeq 正则 `::agent-(\d+)$` 从 agentId 尾段提序号 + 完成卡模板 `#{{seq}}` 徽标(解析失败不渲染)+ agentPeekSubject 浮层头身份行(agent 序号/子会话标题/任务书首行),多代理交错完成可辨认「哪张卡是哪个代理」 # 新能力引入评估纪律(2026-10-04 用户令,长期生效) @@ -1057,7 +914,7 @@ graph TD - [ ] W-h 会话间/代理间感知(FileLeaseRegistry:写前登记+冲突指路/query_leases 工具/兄弟感知/知识联动;与 W-a 组合成完整 lease)——设计 §十;根治今日全部互踩 -- [ ] W-h 感知租约(升级 P0:今日三连互踩实锤,FileLeaseRegistry+写前置校验,小改大收益)——能力批实施第一位 +- [x] W-h 感知租约 ✅已实施核验销账(2026-10-05):lease.rs 345 行全套在案(FileLeaseRegistry+写前置校验+10min TTL 兜底+键归一+互斥/同会话放行/释放/TTL/归一五组单测),query_leases 工具已注册暴露,对齐《并行化与异步唤醒-2026-10-04》§10.1——文档滞后案 - [ ] W-e2 全局并发池(小:子代理并发上限+预算联动) - [ ] W-d1 挂机最小闭环(领单→执行→销账→循环+轻量续接;W-b wake 随其绑定实施) - [ ] 冻结待信号: W-a 写并行吞吐(只先修竞态)/W-d2 自拆解自设计/W-f 编排 2.0-3.0/W-e1 async spawn——启动信号见设计 §十一 @@ -1074,3 +931,83 @@ graph TD - download_file 工具增强候选:失败自动镜像回退(小改,登记待批) - [ ] F-m 实施细节补充(§13.4 有界失败记忆):A4 失败豁免精化为「首条全文+同签名折叠一行(第N次)」——防窗口灌爆+盲试显式计数;失败分册按类去重 Top-K 有界;监控按月轮转;三界类目口径同源(同一归类函数) + +## 派工纪律内置基线(AI-Ops 分析移交 2026-10-05,用户拍板"通用能力内置沉淀") + +> 背景:派工纪律 11 条在 AI-Ops(u-claw 侧)一周实战蒸馏;devflow agent(spawn_agent)同样需要,但走知识库条目会在分发时裸奔(数据不随包,新实例知识库空=纪律失效)。用户拍板:通用能力内置进代码随包分发,知识库只放场景性增强层。 +> 落点:agentic loop 的 system_prompt 组装处内置以下母本(常量,编译进二进制)。u-claw 侧同款内容走 prompt.go 灰度(已拍板路径,分层:1-9 条第一批,10/11 条观察后二批)。 + +### 内置母本(8 行,双端通用,可微调措辞不可删项) + +```text +## 派工纪律 +1. 派工先估颗粒:单任务一轮内自证完成,装不下就拆批;独立子任务并行派。 +2. 任务书三要素:目标文件边界(热点文件单点编辑权)+预期时限+标准验证命令。 +3. 验收认证据:子代理须交"命令+结果",不收"我做完了";主代理独立抽查裁决。 +4. 子代理报告限长(约4千字),超长落盘只给指针;主代理消化后紧凑透出,禁全量转贴。 +5. 超时处置:到点发收敛令→无响应试探存活→确认死则主代理砍维度亲自收尾,勿重派同颗粒。 +6. 改代码禁全量验证:单文件快检,阶段收官统一验证一次。 +7. 派工前查重在途执行体,同类故障只委托一头。 +8. 结果透出结论先行,证据随附,过程不铺陈。 +``` + +### 实施项 + +- [ ] **N1 内置基线**:母本注入 agentic loop system_prompt 组装处(spawn_agent 子代理与编排节点同享;主 agent 自己派工时同样受此约束)。验收:新会话 system_prompt 含该段;知识库为空的全新实例同样生效。 +- [ ] **N2 知识库增强层定位收缩**:纪律类内容不再入知识库条目(避免双源漂移);知识库只留场景性/经验性条目。存量 review_rule 若与母本重叠,以母本为准去重。 +- [ ] **N3 知识库分发缺口登记**(独立项,优先级随多实例节奏):安装包带基线知识包(首启导入)或同步桥提级(存储架构定稿既有的"统一条目模型+同步桥"第一个实战用例,中心=df-pg)。单机阶段可缓,多实例分发前必须解。 +- [ ] **N4 与编排纪律 prompt 合并**:u-claw 侧落点 prompt.go"派工纪律"段与既定"编排纪律 prompt"(07a8c55 同落点)一次发版合并做,勿两次动 system prompt。 + +## u-claw 沉淀能力蒸馏清单(AI-Ops 分析移交 2026-10-05) + +> 来源:u-claw 一年实战沉淀(memory 30+ 条+Go 代码机制)。筛选标准:通用能力(非业务/非容器特有),devflow 有实证差距才立。已对齐不重复立的:显式交付协议(通知桥 oneshot)、PATCH 单点渲染、重连 backoff、查重在途(派工契约第7条)、双头让位。 + +### A 类·机制移植(有实证差距,优先) + +- [x] **D1 僵尸资源显式释放** ✅实施完成(2026-10-05 本批,勘察补充:现 main.rs 尚无重连循环,leak 暂只发生一次,但 SDK start() 退出即任务终局——将来补 backoff 重连时 run() 重入必每次 leak 一份 LarkClient,预防性根治):client 由字段 `LarkClient` 改 `&'static LarkClient`,Box::leak 收敛到 new()/offline() 构造期各一次;run() 重连重入只重建 Channel 复用同一 &'static 引用,注释记载 D1 根因;dispatch 出站链零改动(引用传递同语义)。验收 cargo check -p df-im-gateway EXIT 0 + cargo test -p df-im-gateway 14 passed 0 failed +- [ ] **D2 白名单热加载**(u-claw 已有):现白名单启动时读死进 Ctx(L107),改 KV 须重启进程。修:whitelist 改 RwLock<HashMap> 或每消息现读 KV(读频低,成本可忽略);验收:改 KV 后下一条消息即生效,无需重启。 +- [ ] **D3 渠道心跳保活**(u-claw feishu_beat.go):现靠断线被动重连,静默半开连接(对端假活)检测不到。修:周期(60s)轻量活性探测,连续 N 次失败主动断开触发重连。验收:杀对端连接后 2 分钟内自愈。 + +### B 类·设计思想对齐(随相关批次顺路) + +- [ ] **D4 目标快照+补拉兜底**(u-claw goal.go L1/L2/L3 实证"三战三捷"):停机恢复按快照续,不重放旧目标。V49 断点续跑(loop_state)正在做,补一个维度:恢复时对齐用户当前有效意图(目标快照),防"按旧目标继续跑偏"。 +- [ ] **D5 空轮判据自愈**(u-claw 空轮自愈经验):会话轮次无产出(空文本/无工具调用)连续 N 次→判定卡死,自动止损+提醒,而非挂到超时。 +- [ ] **D6 效率基线滚动机制**(u-claw 1001 基线方法论):季度实测运行基线(吞吐/尾延迟/子代理占比)对照优化,防瓶颈形态迁移后优化失焦。devflow 首个基线已建(2026-10-05),固化为机制。 + +### C 类·功能候选(产品拍板,不急) + +- [ ] D7 媒体收发:图片/文件上下行(SDK upload_image/download 已在,u-claw media.go 先例)——识别截图/回传产物图场景价值高 +- [ ] D8 定时任务面:bot 内 cron 注册/查询/触发(u-claw crontab.go 形态) +- [ ] D9 群成员/用户名缓存:回复带真实姓名(u-claw members/username.go,20min 缓存) +- [ ] D10 会话标题:摘要里第二批已列(title_lifecycle 已有桌面端,渠道侧展示) + + +## 2026-10-05 晚交接:AI-Ops 移交待办(用户拍板:devflow 安排推进) + +> 用户已授权:下列项无需再逐项请示,按看板自行安排推进。攒批提交已授权(分组执行)。 + +### 决策结论(已定,不再议) +- D2d 归档阈值 = 90 天(定案,随 D2a 批实施) +- N3 知识库分发 = 先安装包基线包、后同步桥(多实例分发前 2 周启动) +- 渠道假活(21:19 换装后 WS 未连)= 复发才深挖;D3 看门狗已实测立功(21:43 告警) +- 历史数据默认保留禁清理(全局规则,见 memory feedback-history-data-keep-default) + +### 待办(按优先级) +1. [ ] **攒批提交 47 文件**(已授权):停笔窗口执行,按主题 4 笔分组——①V49 断点续跑批(state/main/lib/sub_agent/df-storage) ②飞书渠道批(feishu_channel 四修+流式+D1-D3) ③纪律内置+知识库批(N1 helpers 等) ④前端流式链批(useStreamRenderer 等)。提交前 cargo check + vue-tsc 终验。 +2. [ ] **C-r0 验证命令后台化**(验收:>90s 同步等待占比 <15%,现状 45%) +3. [ ] **D2a ai_tool_executions 结果过期外置**(DB 193.7MB 半天+17.6MB,紧迫)+ D2d 归档 90 天随批 +4. [ ] **锁面+同步 IO 技术债**(196 处 async 锁抽查、commands/ai 内 5 文件 std::fs 混入) +5. [ ] **注入验证**:知识库 691b351d/c142e6b2 已 published+high,新会话确认铁律段带出 +6. [ ] **V49 批次独立核查**:47 文件收敛后请 AI-Ops 派核查,或自证全量 build+测试 +7. [ ] C 类功能候选(优先级未排):D7 媒体收发 / D8 定时任务面 / D9 用户名缓存 / D10 会话标题 + +### 已完成勿重做(2026-10-05 AI-Ops 侧,勾选凭据) +- N1 派工纪律内置(DISPATCH_DISCIPLINE:mod.rs:184/2791 + sub_agent.rs:489)——已随 21:19 换装上线 +- D1 僵尸 client 泄漏修复 / D2 白名单热加载 / D3 看门狗告警——已上线,D3 实测立功 +- 飞书四修+流式(占位卡片 edit_card 原地更新/表格降级/clip_for_card 超长裁剪/启动日志)——已上线 +- 知识库 691b351d/c142e6b2 已 published+high(c142e6b2 含权威源声明);9c848ab 确认零丢失(rescue 分支锚定) + +### 新缺陷(2026-10-05 22:2x 用户截图实证,与 N/D 清单同级) +8. [ ] **工具输出 ANSI 转义裸露**(乱码):run_command 类工具输出含 ANSI 码(\x1b[7m 等)原样透传前端显示为 ␛[7m。修:执行器统一 strip_ansi(或注入 TERM=dumb/--color=never)+前端渲染层兜底过滤;验收:cargo test 输出在面板无 ␛ 残留。 +9. [ ] **会话标题 content 泄漏**:会话标题显示原始正文片段(如 `<details> <summa…`,84cb104c 会话同症)——标题生成输入未清洗或兜底直接取 content 头部。修:title 生成失败/超时兜底改为清洗后的首行摘要,含 HTML/Markdown 标签时剥离;验收:长会话标题无标签裸露。 +> 附:截图实证单会话 206 轮/1500 万 tokens——C 批次(上下文管理)紧迫性最强活证,随 C-r0 优先消化。 diff --git a/scripts/package-devflow.ps1 b/scripts/package-devflow.ps1 index fda3ca6..7535c3b 100644 --- a/scripts/package-devflow.ps1 +++ b/scripts/package-devflow.ps1 @@ -27,10 +27,10 @@ if ($free -lt $minFreeGB) { Write-Host "[预检] E 盘剩余 ${free}GB,通过" if ($Mode -eq "verify") { - Write-Host "== 通道1: 快速编译验证(cargo build --release,不打安装包) ==" - cmd /c "cargo build --release 2>&1" + Write-Host "== 通道1: 快速编译验证(cargo tauri build --no-bundle,与 full 同形态共享增量缓存) ==" + cmd /c "cargo tauri build --no-bundle 2>&1" if ($LASTEXITCODE -ne 0) { Write-Host "[失败] 编译未过"; exit 1 } - Write-Host "[通过] release 编译成功(未打安装包)" + Write-Host "[通过] tauri 形态编译成功(未打安装包)" exit 0 } diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 68c6164..439c6a9 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -25,6 +25,8 @@ @close-panel="store.togglePanel()" @dock="store.dockDetached()" @toggle-always-on-top="toggleAlwaysOnTop" + @reattach="onReattachToMain" + @show-shortcuts="showShortcuts" @close-detached="store.closeDetachedWindow()" @provider-switched="(name: string) => showToast(t('aiChat.providerSwitched', { model: name }), 'info')" /> @@ -46,6 +48,8 @@ <!-- Input --> <div class="ai-input-area"> + <!-- B4: 自动压缩完成提示(一次性 4s 淡出;治「上下文何时被压缩」零感知,不弹 toast 不打断流) --> + <div v-if="autoCompressedFlash" class="ai-autocompressed-hint">{{ t('aiChat.autoCompressed') }}</div> <!-- 无人值守边界配置卡(默认收起=现状零变化):发起时一次授权,生成中锁编辑 --> <TaskBoundaryCard @toast="(p) => showToast(p.msg, p.type)" /> <!-- AE-2025-07: Agentic 循环进度条(C 状态语义化) @@ -131,6 +135,24 @@ <Transition name="ai-toast"> <div v-if="toast.visible" class="ai-toast" :class="'ai-toast-' + toast.type">{{ toast.msg }}</div> </Transition> + <!-- I12(2026-10-05):键盘快捷键速查。Teleport 到 body 防 .ai-panel overflow 裁剪; + 主窗/分离窗各自实例独立渲染,入口在 TopBar ... 菜单(emit show-shortcuts) --> + <Teleport to="body"> + <div v-if="shortcutsVisible" class="ai-shortcuts-mask" @click.self="shortcutsVisible = false"> + <div class="ai-shortcuts-panel" role="dialog" :aria-label="t('aiChat.shortcutsTitle')"> + <div class="ai-shortcuts-head"> + <span class="ai-shortcuts-title">{{ t('aiChat.shortcutsTitle') }}</span> + <button class="ai-shortcuts-close" :aria-label="t('aiChat.scCloseOverlay')" @click="shortcutsVisible = false">✕</button> + </div> + <ul class="ai-shortcuts-list"> + <li v-for="s in shortcuts" :key="s.key" class="ai-shortcuts-row"> + <span class="ai-shortcuts-key">{{ s.key }}</span> + <span class="ai-shortcuts-act">{{ s.label }}</span> + </li> + </ul> + </div> + </div> + </Teleport> </div> </template> @@ -154,6 +176,8 @@ import { useConfirm } from '../composables/useConfirm' import { useToast } from '../composables/useToast' import { useProjectStore } from '../stores/project' import { collectTurnStats, turnMessages } from '../composables/ai/useTurnTrace' +import { useAiContext } from '../composables/ai/useAiContext' +import { emitReattachToMain, registerMainReattachListener } from '@/composables/ai/useAiWindow' import type { AiMessage } from '../api/types' const props = defineProps<{ @@ -164,6 +188,8 @@ const props = defineProps<{ const store = useAiStore() const { t } = useI18n() const router = useRouter() +// B4: 自动压缩完成一次性可见信号(输入区上方 hint 行短暂显示,4s 自灭) +const { autoCompressedFlash } = useAiContext() // path_auth 审批链阶段3b(统一审批模型):df-ai-unified-approval 开关响应式 ref。 // 开(true,默认):path 挂起归一进 ToolCard 内联审批(once/always/deny),不挂 DirAuthDialog。 @@ -278,7 +304,9 @@ async function confirmNewConversation() { await store.newConversation() } catch (e) { console.error('[AI] 新建对话失败:', e) - // 用错误气泡反馈用户,不静默吞掉 + // #16(2026-10-05 注释纠偏):原注释称「用错误气泡反馈用户」,实际 catch 内仅 console.error, + // UI 无反馈;失败兜底由 store.newConversation 内部 withConvOp toast 承接,此处不再重复提示, + // 保留 console.error 留痕。注释与实现对齐,避免后续维护误导。 } } @@ -338,9 +366,19 @@ function onGlobalKeydown(e: KeyboardEvent) { const el = e.target as HTMLElement | null if (ctrl && !!el?.closest?.('input, textarea, [contenteditable]')) return // Ctrl+N: 新建对话(Tauri 下 preventDefault 可靠,无浏览器新窗口冲突) + // V10(2026-10-05):撤 streaming 守卫,对齐按钮侧 handleNewConversation 的 F-09 真并发 + // 语义——新建不杀旧 loop,旧会话后台继续,无需「生成中禁新建」;原守卫静默吞键(无反馈) + // 与按钮可点自相矛盾。真并发下新旧会话事件按 conversation_id 路由,无串台风险。 if (ctrl && (e.key === 'n' || e.key === 'N')) { e.preventDefault() - if (!store.state.streaming) store.newConversation() + store.newConversation() + return + } + // I13(2026-10-05):Ctrl+I 聚焦输入框(输入焦点守卫已让路——焦点已在输入控件内不重复抢); + // Ctrl+K 归会话搜索,聚焦输入框选 I(无浏览器默认冲突,IME 安全)。 + if (ctrl && (e.key === 'i' || e.key === 'I')) { + e.preventDefault() + chatInputRef.value?.focus() return } // Ctrl+R: 重新生成末条回复(替代浏览器刷新,Tauri 下 preventDefault 可靠) @@ -386,6 +424,12 @@ function onGlobalKeydown(e: KeyboardEvent) { answerConfirm(false) // 视为取消,resolve Promise 打断 confirmClearChat/confirmDelete return } + // I12:快捷键速查面板开时 Esc 仅关面板(优先级高于主面板/分离窗关闭,防误关整窗) + if (shortcutsVisible.value) { + e.preventDefault() + shortcutsVisible.value = false + return + } // P1-2 Esc 仲裁:事件源落在内层 Esc 消费者(输入框/侧栏重命名框/队列编辑框等输入控件, // 或技能/@联想浮层与技能 chip)时,Esc 语义归内层(关浮层/取消编辑/取消输入法组词), // 不关分离窗——否则输入框按 Esc 关技能浮层的同时窗口也进入关闭流程,一次按键双重效果。 @@ -612,6 +656,9 @@ onMounted(async () => { _unlistenAutoApproved = await listen<{ tool: string; dir: string }>('ai-tool-auto-approved-toast', (e) => { showToast(t('aiChat.autoApprovedToast', { tool: e.payload.tool, dir: e.payload.dir }), 'info') }) + // V11b: 主窗口注册跨窗收回分离窗监听(label 判定在内部,分离窗调用为 no-op); + // reattachPanel 由主窗 store 执行(关全部 ai-detached-* 窗 + 恢复主面板)。 + registerMainReattachListener(() => store.reattachPanel()) // P1-e-d: 审批失败 toast(useAiApproval.approveToolCall 审批 IPC 未送达经事件总线广播, // 本组件 listen 弹本地 toast。主窗口/分离窗口各挂一份 AiChat,各自消费) _unlistenAiToast = await listen<{ msg: string; type: 'info' | 'error' | 'warning' | 'success' }>('ai-toast', (e) => { @@ -674,6 +721,30 @@ async function toggleAlwaysOnTop() { appSettingsStore.set('df-ai-always-on-top', alwaysOnTop.value) } +// ── V11b 收回分离窗 + I12 快捷键速查面板 ── +// V11b(2026-10-05):分离窗 TopBar「收回」按钮 emit 链——分离窗与主窗是独立 JS context, +// 不能直调主窗 store.reattachPanel;走 Tauri 全局事件广播(emitReattachToMain),主窗 AiChat +// 挂载时注册的 registerMainReattachListener(useAiWindow.ts,按 label==='main' 判定)消费执行。 +// I12(2026-10-05):顶部 ... 菜单入口(TopBar emit show-shortcuts)→ 此处 Teleport 展示速查浮层。 +// 键位表 shortcuts 与 onGlobalKeydown 同文件同居——键区调整时表随手同步,防速查与真实键位漂移。 +const shortcutsVisible = ref(false) +// computed:随 locale 即时翻译(仅面板打开时消费,开销可忽略);t 已在本组件上方 useI18n 解构 +const shortcuts = computed(() => [ + { key: 'Ctrl+N', label: t('aiChat.scNewConv') }, + { key: 'Ctrl+I', label: t('aiChat.scFocusInput') }, + { key: 'Ctrl+R', label: t('aiChat.scRegenerate') }, + { key: 'Ctrl+L', label: t('aiChat.scClearChat') }, + { key: 'Ctrl+K', label: t('aiChat.scSearch') }, + { key: 'Ctrl+B', label: t('aiChat.scToggleSidebar') }, + { key: 'Esc', label: t('aiChat.scCloseOverlay') }, +]) +function showShortcuts() { + shortcutsVisible.value = true +} +async function onReattachToMain() { + await emitReattachToMain() +} + // ── F-15 阶段2: 手动上下文管理(清空 / 压缩) ── // ChatMessage.status 渲染语义(types.ts AiMessage.status 字面量联合定义): // null | undefined | 'active' → 正常消息(现状) @@ -833,6 +904,21 @@ async function initContextEventListeners(): Promise<void> { flex-shrink: 0; animation: ai-agentic-pulse 1.2s ease-in-out infinite; } +/* B4: 自动压缩完成提示(一次性 4s;视觉克制度对齐 agentic 进度条,弱存在感) */ +.ai-autocompressed-hint { + margin-bottom: 6px; + padding: 3px 8px; + background: color-mix(in srgb, var(--df-accent) 8%, transparent); + border-radius: var(--df-radius); + font-size: 11px; + color: var(--df-text-dim); + user-select: none; + animation: ai-autocompressed-fade 4s ease-in-out forwards; +} +@keyframes ai-autocompressed-fade { + 0%, 70% { opacity: 1; } + 100% { opacity: 0.35; } +} .ai-agentic-progress-text { font-family: var(--df-font-mono); white-space: nowrap; @@ -930,4 +1016,70 @@ async function initContextEventListeners(): Promise<void> { .ai-toast-enter-active, .ai-toast-leave-active { transition: opacity 0.2s, transform 0.2s; } .ai-toast-enter-from, .ai-toast-leave-to { opacity: 0; transform: translate(-50%, -8px); } +/* ═══ I12 快捷键速查面板(Teleport 到 body,scoped 对 Teleport 内容仍生效—— + Vue scoped 把 data 属性打在编译期节点上,与挂载位置无关) ═══ */ +.ai-shortcuts-mask { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} +.ai-shortcuts-panel { + background: var(--df-bg-card); + border: 0.5px solid var(--df-border); + border-radius: var(--df-radius-lg, 8px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + width: 320px; + max-width: calc(100vw - 48px); + padding: 14px 16px; +} +.ai-shortcuts-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} +.ai-shortcuts-title { + font-size: 13px; + font-weight: 600; + color: var(--df-text); +} +.ai-shortcuts-close { + background: transparent; + border: none; + color: var(--df-text-secondary); + cursor: pointer; + font-size: 12px; + padding: 2px 4px; + border-radius: 4px; +} +.ai-shortcuts-close:hover { color: var(--df-text); background: var(--df-bg-secondary); } +.ai-shortcuts-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 6px; +} +.ai-shortcuts-row { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; +} +.ai-shortcuts-key { + font-family: var(--df-mono, monospace); + font-size: 11px; + background: var(--df-bg-secondary); + border: 0.5px solid var(--df-border); + border-radius: 4px; + padding: 2px 8px; + color: var(--df-text); +} +.ai-shortcuts-act { color: var(--df-text-secondary); } + </style> diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index 8eb6417..d048abd 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -84,28 +84,28 @@ :disabled="approving" @click="onAuthorizeOnce"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthOnce') }} </button> <button class="ai-tool-btn ai-tool-btn--session" :disabled="approving" @click="onAuthorizeSession"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthSession') }} </button> <button class="ai-tool-btn ai-tool-btn--always" :disabled="approving" @click="onAuthorizeAlways"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthAlways') }} </button> <button class="ai-tool-btn ai-tool-btn--reject" :disabled="approving" @click="onDeny"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ $t('aiChat.dirAuthDeny') }} </button> </div> @@ -114,14 +114,14 @@ :disabled="approving" @click="onApprove"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiTool.approve') }} </button> <button class="ai-tool-btn ai-tool-btn--reject" :disabled="approving" @click="onReject"> <span v-if="approving" class="ai-tool-btn-spinner" /> - <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ $t('aiTool.reject') }} </button> </div> diff --git a/src/components/ToolCardList.vue b/src/components/ToolCardList.vue index 325ff5c..8501fba 100644 --- a/src/components/ToolCardList.vue +++ b/src/components/ToolCardList.vue @@ -26,6 +26,8 @@ <div v-if="group.calls.length >= 2" class="ai-tool-group-header" @click="toggleGroupCollapse(gi)"> <span class="ai-tool-group-icon" v-html="groupIcon(group.name)"></span> <span class="ai-tool-group-name">{{ groupDisplayName(group) }}</span> + <!-- R7 组头失败徽标:折叠组内失败卡不展开也能从组头发现(红字计数) --> + <span v-if="groupFailCounts[gi] > 0" class="ai-tool-group-fail">{{ $t('aiTool.groupFailCount', { n: groupFailCounts[gi] }) }}</span> <span class="ai-tool-group-chevron" :class="{ 'is-open': !isGroupCollapsed(gi) }">▸</span> </div> <ToolCard @@ -61,6 +63,7 @@ import { listen, type UnlistenFn } from '@tauri-apps/api/event' import type { AiToolCallInfo } from '@/api/types' import { useConfirm } from '@/composables/useConfirm' import { toolRiskLevel } from '@/composables/ai/useToolApproval' +import { isToolFailure } from '@/composables/ai/useToolCard' // useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key) const { t } = useI18n() @@ -110,6 +113,16 @@ const collapsedGroups = ref<Set<number>>(new Set()) // 用户手动展开过的组不再被自动收回——名字比索引稳(分组只追加不重排,但语义上名字更直白)。 const userTouchedGroups = ref(new Set<string>()) +// ── R7 组头失败徽标:折叠组内失败卡扫组头即可发现 ── +// 病灶(R7 单行折叠信息量缺口):工具失败信号散在单卡红边,同名工具组默认折叠后 +// 失败卡被 display:none 压住,10+ 卡需逐组展开逐行扫。轨迹卡聚合行/执行中步骤行 +// 已有「失败 N」计数,唯一盲区是组头。此处补:组内失败卡 ≥1 时组头显红徽标 +// 「{n} 失败」,折叠展开都可见。失败判定复用 useToolCard.isToolFailure 单点(与 +// 单卡红边/轨迹卡统计同源,防双写判定漂移)。 +const groupFailCounts = computed<number[]>(() => + groupedToolCalls.value.map(g => g.calls.reduce((n, tc) => n + (isToolFailure(tc) ? 1 : 0), 0)), +) + interface ToolCallGroup { name: string calls: AiToolCallInfo[] @@ -415,12 +428,12 @@ defineExpose({ collapseInactive, scrollToFirstPending }) /* -- 分组图标(复用 ToolCard 的 toolCategory 逻辑) -- */ function groupIcon(name: string): string { - if (name === 'read_file') return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>' - if (name === 'write_file') return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>' - if (name === 'list_directory') return '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>' - if (name.includes('create')) return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>' - if (name.includes('delete')) return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>' - return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' + if (name === 'read_file') return '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>' + if (name === 'write_file') return '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>' + if (name === 'list_directory') return '<svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>' + if (name.includes('create')) return '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>' + if (name.includes('delete')) return '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>' + return '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' } </script> @@ -501,6 +514,16 @@ function groupIcon(name: string): string { .ai-tool-group-header:hover { background: rgba(255,255,255,0.03); } +/* R7 组头失败徽标:失败计数红字,margin-left auto 推到 chevron 之前右对齐 */ +.ai-tool-group-fail { + margin-left: auto; + margin-right: 2px; + font-size: 10px; + font-weight: 600; + color: var(--df-danger); + opacity: 0.9; + white-space: nowrap; +} .ai-tool-group-icon { display: flex; align-items: center; diff --git a/src/components/ToolResultBody.vue b/src/components/ToolResultBody.vue index 8396961..6d8dfaf 100644 --- a/src/components/ToolResultBody.vue +++ b/src/components/ToolResultBody.vue @@ -113,11 +113,16 @@ <!-- write_file 结果 --> <div v-else-if="tc.name === 'write_file' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-write-result"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--df-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--df-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> <span class="ai-tool-write-path" :title="parsed?.path">{{ shortPath(parsed?.path || '') }}</span> <span class="ai-tool-write-meta">{{ formatBytes(parsed?.bytes_written) }} {{ $t('aiTool.written') }}</span> <span v-if="typeof parsed?.old_size === 'number' && parsed.old_size > 0" class="ai-tool-write-meta">{{ $t('aiTool.writeOverwrite', { old: formatBytes(parsed.old_size), neww: formatBytes(parsed?.bytes_written) }) }}</span> <span v-if="typeof parsed?.encoding === 'string' && parsed.encoding !== 'utf-8'" class="ai-tool-write-meta">{{ $t('aiTool.encodingLabel', { enc: parsed.encoding }) }}</span> + <!-- R12(2026-10-05):覆盖写入的行级 diff 回看(后端 diff 字段,新建/base64 为 null 不渲染) --> + <div v-if="resultDiffLines.length" class="ai-tool-write-diff"> + <pre class="ai-tool-diff-pre"><code><span v-for="(ln, idx) in resultDiffLines" :key="idx" class="ai-tool-diff-line" :class="'ai-tool-diff-line--' + ln.kind">{{ ln.text }}{{ '\n' }}</span></code></pre> + <button v-if="diffTotal > DIFF_PREVIEW_LINES" type="button" class="ai-tool-diff-toggle" @click="diffExpanded = !diffExpanded">{{ diffExpanded ? $t('aiTool.diffCollapse') : $t('aiTool.diffExpand', { n: diffTotal }) }}</button> + </div> </div> <!-- patch_file 结果:文件路径/字节增减 + unified diff 红删绿增行级预览(复用 ai-tool-diff-pre/--add/--del/--ctx 既有样式) @@ -132,6 +137,8 @@ <div v-if="parsed?.warning" class="ai-tool-approval-reason">⚠ {{ parsed.warning }}</div> <div v-if="resultDiffLines.length" class="ai-tool-approval-diff"> <pre class="ai-tool-diff-pre"><code><span v-for="(ln, idx) in resultDiffLines" :key="idx" class="ai-tool-diff-line" :class="'ai-tool-diff-line--' + ln.kind">{{ ln.text }}{{ '\n' }}</span></code></pre> + <!-- R12a:长 diff 展开入口(默认预览 120 行,点开全量) --> + <button v-if="diffTotal > DIFF_PREVIEW_LINES" type="button" class="ai-tool-diff-toggle" @click="diffExpanded = !diffExpanded">{{ diffExpanded ? $t('aiTool.diffCollapse') : $t('aiTool.diffExpand', { n: diffTotal }) }}</button> </div> </div> @@ -202,7 +209,7 @@ <div v-if="obscuraMissing" class="ai-tool-obscura-hint"> <div class="ai-tool-obscura-hint-text">⚠ {{ parsed?.hint || $t('aiTool.fetchUrlFallbackHint') }}</div> <button class="ai-tool-btn ai-tool-btn--install" @click="onInstallObscura"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> {{ $t('aiTool.installObscura') }} </button> </div> @@ -331,10 +338,15 @@ const cmdOutput = computed(() => { }) /** - * patch_file 结果 diff 行拆解(复用 parseDiffLines +add/-del/ ctx 着色)。 - * 数据源:parsed.diff(patch_file 工具执行结果字段)。长 diff 截断到前 120 行防撑爆卡片。 + * patch_file / write_file 结果 diff 行拆解(复用 parseDiffLines +add/-del/ ctx 着色)。 + * 数据源:parsed.diff(工具执行结果字段,write_file 覆盖写入时后端同款生成)。 + * R12a:默认截断前 120 行防撑爆卡片,expanded 后全量渲染(toggle 入口在 diff 块尾部)。 */ -const resultDiffLines = computed(() => parseDiffLines(parsed.value?.diff, 120)) +const DIFF_PREVIEW_LINES = 120 +const diffExpanded = ref(false) +const resultDiffLines = computed(() => parseDiffLines(parsed.value?.diff, diffExpanded.value ? undefined : DIFF_PREVIEW_LINES)) +/** diff 全量行数(不受预览截断影响),驱动展开按钮显隐与行数文案 */ +const diffTotal = computed(() => parseDiffLines(parsed.value?.diff).length) /** * grep 命中计数标签G-1 复用 useToolCard.formatGrep 单一函数, @@ -864,5 +876,20 @@ watch(() => props.tc.status, (s) => { font-family: var(--df-font-sans); transition: filter 0.15s var(--df-ease); } -.ai-tool-btn:hover { filter: brightness(1.1); } -</style> + .ai-tool-btn:hover { filter: brightness(1.1); } + +/* R12: write_file diff 回看 + 长 diff 展开按钮 */ +.ai-tool-write-diff { width: 100%; margin-top: 6px; } +.ai-tool-diff-toggle { + display: block; + margin: 4px auto 0; + background: transparent; + border: 0.5px dashed var(--df-border); + border-radius: 4px; + color: var(--df-text-secondary); + font-size: 11px; + padding: 3px 12px; + cursor: pointer; +} +.ai-tool-diff-toggle:hover { color: var(--df-text); background: var(--df-bg-secondary); } + </style> diff --git a/src/components/ai/ApprovalOverlay.vue b/src/components/ai/ApprovalOverlay.vue index 6c1dd3b..9b7c281 100644 --- a/src/components/ai/ApprovalOverlay.vue +++ b/src/components/ai/ApprovalOverlay.vue @@ -16,7 +16,7 @@ @click="onActivate" > <div class="approval-overlay-icon"> - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M9 11l3 3L22 4" /> <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" /> </svg> @@ -31,7 +31,7 @@ :title="$t('common.close')" @click.stop="onDismiss" > - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18" /> <line x1="6" y1="6" x2="18" y2="18" /> </svg> diff --git a/src/components/ai/ChatInput.vue b/src/components/ai/ChatInput.vue index f2d20f7..6e4c254 100644 --- a/src/components/ai/ChatInput.vue +++ b/src/components/ai/ChatInput.vue @@ -53,7 +53,18 @@ :aria-label="$t('aiChat.pickImage')" @click="onPickImage" > - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> + <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> + </button> + <!-- I9(2026-10-05):文本/代码文件附件入口——选文件读内容以 ```<file> 块插入草稿, + 走既有文本发送链,零协议改动;大小/行数上限防刷屏 --> + <button + type="button" + class="ai-img-pick-btn" + :title="$t('aiChat.pickFileTitle')" + :aria-label="$t('aiChat.pickFileTitle')" + @click="onPickTextFile" + > + <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg> </button> <!-- L2 状态机停止按钮三态(批2 1c):conv_state 派生 generating/stopping/error 三态, idle(或 conv_state 未收到回退旧 streaming=false)显发送按钮。双轨过渡不强制全替旧 bool。 --> @@ -63,7 +74,7 @@ :title="$t('aiChat.stopping')" disabled > - <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> </button> <button v-else-if="stopBtnState === 'retry'" @@ -71,7 +82,7 @@ :title="$t('aiChat.stopFailedRetry')" @click="store.stopChat" > - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg> </button> <button v-else-if="stopBtnState === 'stop'" @@ -79,7 +90,7 @@ :title="$t('aiChat.stopGenerating')" @click="store.stopChat" > - <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> </button> <button v-else @@ -88,7 +99,7 @@ :disabled="!canSend" @click="handleSend" > - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg> </button> </div> <!-- ⑥.4 Phase 4: @项目展开摘要(提取至 EnrichmentPanel.vue) --> @@ -205,6 +216,66 @@ async function addImageFile(file: File): Promise<void> { } } +/** I9(2026-10-05):「📄 文件」按钮——选文本/代码文件读内容,以代码块形式插入输入框草稿。 + * 病灶:附件通道仅图片,文本/代码文件无入口(粘贴/拖入均仅认 image/)。用户要给 AI 看一段 + * 代码/配置只能手动开文件复制。本方案走纯前端:读文件内容插入草稿,复用既有文本发送链, + * 零协议/后端改动。多选逐个插入;单文件 256KB/5000 行上限(超出截断并提示,防撑爆上下文)。 + * 扩展名白名单取常见文本/代码/配置格式,二进制格式(png/pdf/zip 等)不在名单直接拒绝。 */ +const TEXT_FILE_EXTENSIONS = [ + 'txt', 'md', 'markdown', 'json', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf', 'env', 'csv', 'tsv', + 'js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'vue', 'svelte', 'css', 'scss', 'less', 'html', 'htm', 'xml', 'svg', + 'py', 'rs', 'go', 'java', 'kt', 'kts', 'c', 'h', 'cpp', 'hpp', 'cc', 'hh', 'cs', 'swift', 'm', 'mm', + 'rb', 'php', 'pl', 'pm', 'lua', 'sh', 'bash', 'zsh', 'fish', 'ps1', 'psm1', 'bat', 'cmd', 'sql', 'r', + 'dart', 'gradle', 'cmake', 'dockerfile', 'makefile', 'gitignore', 'editorconfig', 'properties', 'proto', 'graphql', 'prisma', +] +const MAX_TEXT_FILE_BYTES = 256 * 1024 +const MAX_TEXT_FILE_LINES = 5000 +async function onPickTextFile(): Promise<void> { + const selected = await open({ + multiple: true, + title: t('aiChat.pickFileTitle'), + filters: [{ name: t('aiChat.textFileFilter'), extensions: TEXT_FILE_EXTENSIONS }], + }) + if (!selected) return + const files = Array.isArray(selected) ? selected : [selected] + for (const path of files) await insertTextFileDraft(path) +} + +/** 读单个文本文件 → 截断(超限提示)→ 以 fenced code block 插入草稿(光标回到块尾) */ +async function insertTextFileDraft(path: string): Promise<void> { + try { + // 复用图片链的 blobFromPath(convertFileSrc→fetch),项目未装 plugin-fs 零新依赖 + const fileObj = await blobFromPath(path) + if (!fileObj) throw new Error('blobFromPath returned null') + const content = await fileObj.text() + const fileName = path.split(/[\\/]/).pop() || path + let text = content + let truncated = false + if (content.length > MAX_TEXT_FILE_BYTES) { + text = content.slice(0, MAX_TEXT_FILE_BYTES) + truncated = true + } + const lines = text.split('\n') + if (lines.length > MAX_TEXT_FILE_LINES) { + text = lines.slice(0, MAX_TEXT_FILE_LINES).join('\n') + truncated = true + } + // 文件名后缀作语言提示(无扩展名/特殊名不给语言,裸 ``` 也够用) + const ext = fileName.includes('.') ? (fileName.split('.').pop() || '').toLowerCase() : '' + const lang = TEXT_FILE_EXTENSIONS.includes(ext) ? ext : '' + const block = '\n\n```' + lang + ' <!-- ' + fileName + ' -->\n' + text + (truncated ? '\n… ' + t('aiChat.fileTooLargeTruncated') : '') + '\n```\n' + inputText.value += block + await nextTick() + inputEl.value?.focus() + const el = inputEl.value + if (el) el.selectionStart = el.selectionEnd = el.value.length + } catch (e) { + // 用户取消读取/二进制解码失败/权限拒绝:提示后继续(多选时不阻断后续文件) + emit('error', { msg: t('aiChat.fileReadFailed'), type: 'warning' }) + console.error('[AI] 文本文件读取失败:', path, e) + } +} + /** "📎 图片"按钮:tauri 原生文件对话框选图(多选,仅图片)→ 逐个 addImageFile。 */ async function onPickImage(): Promise<void> { const selected = await open({ @@ -727,7 +798,7 @@ function selectMention(item: MentionItem) { el.focus() el.setSelectionRange(pos, pos) el.style.height = 'auto' - el.style.height = Math.min(el.scrollHeight, 120) + 'px' + el.style.height = Math.min(el.scrollHeight, INPUT_MAX_HEIGHT) + 'px' } }) } @@ -830,11 +901,15 @@ function handleKeydown(e: KeyboardEvent) { } } +/** 输入框最大高度(CSS .ai-input max-height 同源;I11:原 JS 120 vs CSS 200 不一致, + * 用户手工拉伸到 200px 后任意按键触发 autoResize 被压回 120px,违反 resize:vertical 预期) */ +const INPUT_MAX_HEIGHT = 200 + function autoResize() { const el = inputEl.value if (el) { el.style.height = 'auto' - el.style.height = Math.min(el.scrollHeight, 120) + 'px' + el.style.height = Math.min(el.scrollHeight, INPUT_MAX_HEIGHT) + 'px' } } @@ -1022,7 +1097,7 @@ function fillDraft(text: string, skill: SkillInfo | null = null): void { const len = el.value.length el.setSelectionRange(len, len) el.style.height = 'auto' - el.style.height = Math.min(el.scrollHeight, 120) + 'px' + el.style.height = Math.min(el.scrollHeight, INPUT_MAX_HEIGHT) + 'px' } }) } diff --git a/src/components/ai/ConversationSidebar.vue b/src/components/ai/ConversationSidebar.vue index b7ad574..cd02f0a 100644 --- a/src/components/ai/ConversationSidebar.vue +++ b/src/components/ai/ConversationSidebar.vue @@ -15,12 +15,12 @@ <div class="ai-conv-sidebar-header"> <span class="ai-conv-sidebar-title">{{ t('aiChat.sidebarTitle') }}</span> <button class="ai-btn-icon" @click="emit('confirm-new-conversation')" :title="t('aiChat.newConversation')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> </button> </div> <!-- UX-06 §3.1: 对话搜索框(v-model 双绑 store.searchQuery;Ctrl+K 聚焦) --> <div class="ai-conv-search"> - <svg class="ai-conv-search-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> + <svg aria-hidden="true" class="ai-conv-search-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> <input ref="searchInputRef" v-model="store.state.searchQuery" @@ -73,7 +73,7 @@ <span v-if="pendingCountOf(conv.id) > 0" class="ai-conv-approve-badge" :title="t('aiChat.pendingApprovalCount', { n: pendingCountOf(conv.id) })">{{ pendingCountOf(conv.id) }}</span> <!-- 无人值守边界徽标:该会话已授权任务边界(镜像渐进填充);点击随会话切回显 --> <span v-if="hasBoundary(conv.id)" class="ai-conv-boundary-badge" :title="t('ai.taskBoundary.badge')"> - <svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> + <svg aria-hidden="true" width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> </span> </span> <span class="ai-conv-item-time">{{ formatRelative(conv.updated_at) }}</span> @@ -81,23 +81,23 @@ <div class="ai-conv-item-actions"> <div class="ai-conv-item-more"> <button class="ai-conv-item-act" @click.stop="toggleActionsMenu(conv.id)" :title="t('aiChat.moreActions')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> </button> <div v-if="actionsOpenId === conv.id" class="ai-conv-actions-menu"> <button class="ai-conv-actions-opt" @click.stop="startRename(conv); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg> {{ t('aiChat.renameConversation') }} </button> <button class="ai-conv-actions-opt" @click.stop="store.setPinnedConversation(conv.id, !conv.pinned); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> {{ conv.pinned ? t('aiChat.unpinConversation') : t('aiChat.pinConversation') }} </button> <button class="ai-conv-actions-opt" @click.stop="store.archiveConversation(conv.id, !conv.archived); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="10" y1="13" x2="14" y2="13"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="10" y1="13" x2="14" y2="13"/></svg> {{ conv.archived ? t('aiChat.unarchive') : t('aiChat.archive') }} </button> <button class="ai-conv-actions-opt ai-conv-actions-opt--danger" @click.stop="emit('confirm-delete', conv.id); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ t('common.delete') }} </button> </div> @@ -156,7 +156,7 @@ > {{ conv.title || t('aiChat.newConversation') }} <!-- UX-2025-20 §7.4:AI 生成标题标识小图标(标题非空非占位时显示) --> - <svg + <svg aria-hidden="true" v-if="conv.title && titleFlash && conv.id === store.state.activeConversationId" class="ai-conv-item-title-badge" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" @@ -167,7 +167,7 @@ <span v-if="pendingCountOf(conv.id) > 0" class="ai-conv-approve-badge" :title="t('aiChat.pendingApprovalCount', { n: pendingCountOf(conv.id) })">{{ pendingCountOf(conv.id) }}</span> <!-- 无人值守边界徽标:该会话已授权任务边界(镜像渐进填充);点击随会话切回显 --> <span v-if="hasBoundary(conv.id)" class="ai-conv-boundary-badge" :title="t('ai.taskBoundary.badge')"> - <svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> + <svg aria-hidden="true" width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> </span> </span> <span class="ai-conv-item-time">{{ formatRelative(conv.updated_at) }}</span> @@ -175,23 +175,23 @@ <div class="ai-conv-item-actions"> <div class="ai-conv-item-more"> <button class="ai-conv-item-act" @click.stop="toggleActionsMenu(conv.id)" :title="t('aiChat.moreActions')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> </button> <div v-if="actionsOpenId === conv.id" class="ai-conv-actions-menu"> <button class="ai-conv-actions-opt" @click.stop="startRename(conv); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg> {{ t('aiChat.renameConversation') }} </button> <button class="ai-conv-actions-opt" @click.stop="store.setPinnedConversation(conv.id, !conv.pinned); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> {{ conv.pinned ? t('aiChat.unpinConversation') : t('aiChat.pinConversation') }} </button> <button class="ai-conv-actions-opt" @click.stop="store.archiveConversation(conv.id, true); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="10" y1="13" x2="14" y2="13"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="10" y1="13" x2="14" y2="13"/></svg> {{ t('aiChat.archive') }} </button> <button class="ai-conv-actions-opt ai-conv-actions-opt--danger" @click.stop="emit('confirm-delete', conv.id); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ t('common.delete') }} </button> </div> @@ -225,7 +225,7 @@ <span v-if="pendingCountOf(conv.id) > 0" class="ai-conv-approve-badge" :title="t('aiChat.pendingApprovalCount', { n: pendingCountOf(conv.id) })">{{ pendingCountOf(conv.id) }}</span> <!-- 无人值守边界徽标:该会话已授权任务边界(镜像渐进填充);点击随会话切回显 --> <span v-if="hasBoundary(conv.id)" class="ai-conv-boundary-badge" :title="t('ai.taskBoundary.badge')"> - <svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> + <svg aria-hidden="true" width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 4 5v6c0 5 3.4 9.7 8 11 4.6-1.3 8-6 8-11V5l-8-3z"/></svg> </span> </span> <span class="ai-conv-item-time">{{ formatRelative(conv.updated_at) }}</span> @@ -233,19 +233,19 @@ <div class="ai-conv-item-actions"> <div class="ai-conv-item-more"> <button class="ai-conv-item-act" @click.stop="toggleActionsMenu(conv.id)" :title="t('aiChat.moreActions')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg> </button> <div v-if="actionsOpenId === conv.id" class="ai-conv-actions-menu"> <button class="ai-conv-actions-opt" @click.stop="store.setPinnedConversation(conv.id, !conv.pinned); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" :fill="conv.pinned ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24V16a1 1 0 001 1h12a1 1 0 001-1v-.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V7a1 1 0 011-1 2 2 0 002-2V3H6v1a2 2 0 002 2 1 1 0 011 1z"/></svg> {{ conv.pinned ? t('aiChat.unpinConversation') : t('aiChat.pinConversation') }} </button> <button class="ai-conv-actions-opt" @click.stop="store.archiveConversation(conv.id, false); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="12" y1="13" x2="12" y2="17"/><polyline points="10 15 12 13 14 15"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v9a2 2 0 002 2h12a2 2 0 002-2V9"/><line x1="12" y1="13" x2="12" y2="17"/><polyline points="10 15 12 13 14 15"/></svg> {{ t('aiChat.unarchive') }} </button> <button class="ai-conv-actions-opt ai-conv-actions-opt--danger" @click.stop="emit('confirm-delete', conv.id); closeActionsMenu()"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ t('common.delete') }} </button> </div> @@ -293,7 +293,7 @@ import { useAiStore } from '../../stores/ai' import { convPendingApprovals } from '../../composables/ai/aiShared' // 无人值守边界徽标只读消费:getConvBoundary 读镜像(reactive Map,模板内读取可追踪; // 镜像由边界配置卡在切会话/写入时渐进填充,无批量 IPC 属契约限制) -import { getConvBoundary } from '../../composables/ai/useTaskBoundary' +import { getConvBoundary, fetchConvBoundary } from '../../composables/ai/useTaskBoundary' // UX走查 P2-7 只读订阅:rename/archive/pin 失败通知由 useAiConversations 发布,本组件弹就近 toast import { convOpFailNotice } from '../../composables/ai/useAiConversations' import { useToast } from '../../composables/useToast' @@ -609,8 +609,23 @@ onMounted(() => { document.addEventListener('keydown', onActionsEscClose) // V1:后台会话待审批变化兜底轮询(见 pendingCountByConv 注) pendingPollTimer = setInterval(() => { pendingTick.value++ }, 2500) + // A10 边界盾标跨窗/重启空洞补拉:镜像 Map 是本窗口 JS context 内存态,分离窗口授权 + // 或重启后(boundary_json 在库)侧栏盾标全消失。挂载时对列表会话补拉一轮填充镜像 + // (fetch 静默降级:失败仅缺徽标不报错;已入镜像的会话跳过,不重复打 IPC)。 + void hydrateBoundaryBadges() }) +/** A10:对侧栏全部会话补拉边界镜像(盾标徽标数据源)。 + * 逐条并发 fire-and-forget;镜像已有条目跳过(配置卡/前次补拉已填,省 IPC)。 + * 列表为空或全命中时零 IPC 开销。 */ +async function hydrateBoundaryBadges(): Promise<void> { + const ids = store.state.conversations.map(c => c.id) + await Promise.allSettled(ids.map(id => { + if (getConvBoundary(id) !== null) return Promise.resolve() + return fetchConvBoundary(id) + })) +} + onBeforeUnmount(() => { document.removeEventListener('click', onActionsOutsideClick) document.removeEventListener('keydown', onActionsEscClose) diff --git a/src/components/ai/EmptyState.vue b/src/components/ai/EmptyState.vue index 49aa7de..4620d81 100644 --- a/src/components/ai/EmptyState.vue +++ b/src/components/ai/EmptyState.vue @@ -34,19 +34,19 @@ const examplePrompts = [ <div class="ai-empty"> <template v-if="!hasProvider"> <div class="ai-empty-icon"> - <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--df-warning)" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg> + <svg aria-hidden="true" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--df-warning)" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg> </div> <p class="ai-empty-text">{{ $t('aiChat.emptyNoProvider') }}</p> <p class="ai-empty-sub">{{ $t('aiChat.emptyNoProviderHint') }}</p> <!-- 跳 Settings:嵌入模式用 router.push;分离窗口无 Settings 路由,降级 toast 提示去主窗口 --> <button class="ai-empty-action" @click="emit('go-to-settings')"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> <span>{{ $t('aiChat.goToSettings') }}</span> </button> </template> <template v-else> <div class="ai-empty-icon"> - <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--df-text-dim)" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/><line x1="9" y1="21" x2="15" y2="21"/></svg> + <svg aria-hidden="true" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--df-text-dim)" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/><line x1="9" y1="21" x2="15" y2="21"/></svg> </div> <p class="ai-empty-text">{{ $t('aiChat.emptyTitle') }}</p> <p class="ai-empty-sub">{{ $t('aiChat.emptyHint') }}</p> diff --git a/src/components/ai/MessageItem.vue b/src/components/ai/MessageItem.vue index addc404..3ab6355 100644 --- a/src/components/ai/MessageItem.vue +++ b/src/components/ai/MessageItem.vue @@ -34,14 +34,11 @@ defineProps<{ msg: AiMessage /** 是否显示编辑按钮(父合成:末条 user + 非生成中 + 非编辑中) */ canEdit?: boolean - /** I6:是否显示重新生成按钮(父合成同 canEdit 时机;作用于末条 user 触发的回复) */ - canRegenerate?: boolean }>() const emit = defineEmits<{ (e: 'copy', msg: AiMessage): void (e: 'start-edit', msg: AiMessage): void - (e: 'regenerate', msg: AiMessage): void }>() // ── Input Augmentation: 按 mention 区间切段 ── @@ -106,7 +103,7 @@ function imgAlt(img: ImageContentPart): string { <!-- 用户消息(本组件唯一渲染角色;assistant/system/error 由 MessageList 内联渲染) --> <div class="ai-msg-user"> <div class="ai-msg-avatar ai-msg-avatar--user"> - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> </div> <div class="ai-msg-bubble ai-msg-bubble--user"> <!-- 问题3 技能调用留痕:前导 /技能名 命中已安装技能 → 紫色 chip 标识,替代原文前缀显示 --> @@ -125,16 +122,12 @@ function imgAlt(img: ImageContentPart): string { <img v-for="(img, idx) in msgImageParts(msg)" :key="msg.id + '-img-' + idx" :src="imageSrc(img)" :alt="imgAlt(img)" class="ai-msg-image" loading="lazy" /> </div> </template> - <!-- I6:重新生成按钮(hover,编辑按钮左侧);复用后端 regenerate 链路(弹末尾 AI 回复取触发 user 消息重跑) --> - <button v-if="canRegenerate" class="ai-msg-regen-btn" :title="t('aiChat.regenerate')" @click.stop="emit('regenerate', msg)"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> - </button> <!-- UX-09:末条 user 消息编辑按钮(hover 显示),点击经 start-edit 链回填输入框编辑重发 --> - <button v-if="canEdit" class="ai-msg-edit-btn" :title="t('aiChat.editMessage')" @click.stop="emit('start-edit', msg)"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> + <button v-if="canEdit" class="ai-msg-edit-btn" :title="t('aiChat.editMessage')" :aria-label="t('aiChat.editMessage')" @click.stop="emit('start-edit', msg)"> + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> </button> - <button class="ai-copy-btn" :title="t('aiChat.copyMsg')" @click.stop="emit('copy', msg)"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg> + <button class="ai-copy-btn" :title="t('aiChat.copyMsg')" :aria-label="t('aiChat.copyMsg')" @click.stop="emit('copy', msg)"> + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg> </button> </div> </div> @@ -202,9 +195,8 @@ function imgAlt(img: ImageContentPart): string { max-height: 200px; border-radius: 6px; } -/* 复制/编辑/重新生成按钮(hover 显示,同栏同视觉;编辑/重新生成仅各自 can* 时渲染) */ +/* 复制/编辑按钮(hover 显示,同栏同视觉;编辑仅 canEdit 时渲染) */ .ai-copy-btn, -.ai-msg-regen-btn, .ai-msg-edit-btn { opacity: 0; transition: opacity 0.15s; @@ -215,11 +207,10 @@ function imgAlt(img: ImageContentPart): string { color: var(--df-text-dim); } .ai-msg-user:hover .ai-copy-btn, -.ai-msg-user:hover .ai-msg-regen-btn, -.ai-msg-user:hover .ai-msg-edit-btn { +.ai-msg-user:hover .ai-msg-edit-btn, +/* UX第五轮 P2-M1:键盘聚焦可达(hover-only 修复)——Tab 聚到按钮或按钮内聚焦时显形 */ +.ai-msg-user:focus-within .ai-copy-btn, +.ai-msg-user:focus-within .ai-msg-edit-btn { opacity: 1; } -.ai-msg-regen-btn:hover { - color: var(--df-accent); -} </style> diff --git a/src/components/ai/MessageList.vue b/src/components/ai/MessageList.vue index af4c45b..8771622 100644 --- a/src/components/ai/MessageList.vue +++ b/src/components/ai/MessageList.vue @@ -12,9 +12,9 @@ //! - emit('start-edit', msg):UX-09 编辑末条 user,父置 editingMsgId + 委托 ChatInput.startEditFocus //! - emit('send-example-prompt', i18nKey):空状态示例问题,父委托 ChatInput.sendExamplePrompt //! - emit('go-to-settings'):无 provider 引导 / 错误气泡「去设置」,父走 router/toast(detached 降级) -//! - emit('toast', {msg, type}):copy/regenerate/retry 等的 toast 转发(父持单一 toast 源) +//! - emit('toast', {msg, type}):copy/retry 等的 toast 转发(父持单一 toast 源) //! - expose(scrollToBottom):父 ChatInput @sent 后调,滚动到底 -import { ref, computed, nextTick, onBeforeUnmount, watch } from 'vue' +import { ref, computed, nextTick, onBeforeUnmount, watch, type Ref } from 'vue' import { useI18n } from 'vue-i18n' import { useAiStore } from '../../stores/ai' import { useMarkdown } from '../../composables/useMarkdown' @@ -206,7 +206,12 @@ const LOAD_MORE_TOP_PX = 60 async function handleMessagesScroll(): Promise<void> { // 原 @scroll 职责(回底按钮显隐 + 底部边沿检测回调收起旧工具卡)保持 - onMessagesScroll(() => collapseAllToolLists(buildActiveToolIds(store.state.messages))) + // R10(2026-10-05):收起旧卡加 isFollowingBottom 门——上滚读历史时新轮次/新卡到达 + // 不再强制收起正浏览的工具卡(跟随意图锁存语义对齐 useMessageScroll.isFollowingBottom); + // 用户在底部跟随态时行为不变(自动收起聚焦新生成)。 + onMessagesScroll(() => { + if (isFollowingBottom.value) collapseAllToolLists(buildActiveToolIds(store.state.messages)) + }) // G3.5:滚顶阈值触发加载更早历史 const cur = loadMoreCursor if (!cur.hasMore || cur.loading || cur.earliestSeq == null) return @@ -373,7 +378,8 @@ watch( const isFirst = lastMsgSnapshot === '' lastMsgSnapshot = snap if (isFirst) return - collapseAllToolLists(buildActiveToolIds(store.state.messages)) + // R10 同门:非跟随态(上滚阅读中)不强制收起正浏览的工具卡 + if (isFollowingBottom.value) collapseAllToolLists(buildActiveToolIds(store.state.messages)) }, ) @@ -383,7 +389,8 @@ watch( () => store.state.agentRound, (cur, prev) => { if (cur <= (prev ?? 0)) return - collapseAllToolLists(buildActiveToolIds(store.state.messages)) + // R10 同门:非跟随态(上滚阅读中)新轮次到达不强制收起正浏览的工具卡 + if (isFollowingBottom.value) collapseAllToolLists(buildActiveToolIds(store.state.messages)) }, ) @@ -724,15 +731,6 @@ async function handleRetry(): Promise<void> { } } -/** - * I6 重新生成按钮:与错误气泡「重试」同链路(store.regenerate,后端弹末尾 AI 回复取触发 - * user 消息重跑),入口场景不同——用户对已有回复不满意主动重问,挂在末条 user 气泡 hover。 - * 与隐藏快捷键 Ctrl+R(AiChat.vue)等价,补齐 UI 入口(i18n aiChat.regenerate 文案已备)。 - */ -async function handleRegenerate(): Promise<void> { - await handleRetry() -} - /** * fetch_url obscura 未装时"AI 帮装"按钮:自动触发 AI 对话让 AI run_command 装流式。 * 提示语足够具体(给出 cargo install + GitHub releases 两种装法 + 装完告知用户重试), @@ -791,28 +789,34 @@ const messageSegments = computed<MessageSegment[]>(() => { return segments }) -/// 展开的折叠段 key 集合(点击分隔条切换);默认折叠 -const expandedSegmentIds = ref<Set<string>>(new Set()) -function toggleSegment(key: string): void { - const next = new Set(expandedSegmentIds.value) - if (next.has(key)) next.delete(key) - else next.add(key) - expandedSegmentIds.value = next +// ── 展开/切换态集合工厂(DRY 2026-10-05) ── +// 五组「Set 集合 + 同构 toggle」(折叠段/长消息/JSON 块/轨迹卡/思考 pill)此前五连逐字重复, +// 收敛本工厂。ids 返回 Ref<Set> 本体:script 侧 .value.has / 整体重置赋值 / 模板侧自动解包 +// .has() 三种既有用法全部零改动,仅收敛重复的 toggle 实现。 +// 不抽独立 composable 文件:五处仅本组件消费,文件内 DRY 即可,跨文件反增跳转成本。 +function useToggleSet(): { ids: Ref<Set<string>>; toggle: (id: string) => void } { + const s = ref<Set<string>>(new Set()) + return { + ids: s, + toggle: (id) => { + const next = new Set(s.value) + if (next.has(id)) next.delete(id) + else next.add(id) + s.value = next + }, + } } +/// 展开的折叠段 key 集合(点击分隔条切换);默认折叠 +const { ids: expandedSegmentIds, toggle: toggleSegment } = useToggleSet() + // M1:assistant 纯文本气泡超长折叠,避免单条 AI 回复撑爆对话区。 // 仅对「已完成(非流式)」assistant 文本生效;流式中的消息不折叠(delta 重排抖动)。 // 阈值 1500 字符:约 markdown 渲染后一屏(400px@13px/1.6≈35行)的源文本量,超此即判长。 // 折叠 max-height 同步 400px(见 CSS .ai-msg-bubble--collapsed,两处一致)。 const LONG_MSG_THRESHOLD = 1500 /// 展开的长消息 id 集合(点击底部按钮切换);默认折叠 -const expandedLongIds = ref<Set<string>>(new Set()) -function toggleLong(id: string): void { - const next = new Set(expandedLongIds.value) - if (next.has(id)) next.delete(id) - else next.add(id) - expandedLongIds.value = next -} +const { ids: expandedLongIds, toggle: toggleLong } = useToggleSet() function isLongMsg(msg: AiMessage): boolean { return msg.role === 'assistant' && !!msg.content && msg.content.length > LONG_MSG_THRESHOLD } @@ -825,6 +829,15 @@ function shouldCollapseLong(msg: AiMessage): boolean { return true } +/// 折叠态是否跳过 markdown 渲染(B5 惰性渲染):折叠气泡视觉上只有 400px 高的 CSS 裁剪, +/// 但 v-html 仍全量渲染整段 markdown(150+ msg 长会话切换/挂载成本随消息数线性涨的主因)。 +/// 跳过渲染后折叠态只显示首屏预览(前 800 字符)——展开瞬间才付全量渲染成本(一次性,可接受)。 +/// 注意:预览截断可能断在 markdown 语法中间(半代码栅栏/半表格),渲染器按普通文本处理无副作用, +/// 仅视觉上「预览尾部可能带半个语法符号」;展开即见完整内容,不误导。 +function collapsedPreviewHtml(msg: AiMessage): string { + return renderMdDecorated(msg.content.slice(0, 800)) +} + // R3:折叠条带量 —— 折叠态按钮显示「已折叠 N 字符 / 约 M tokens,点击展开」。 // 折叠量按 M1 的标定估算(400px 视口 ≈ 阈值字符量,见 LONG_MSG_THRESHOLD 注), // 即 content.length - 阈值;token 为粗估(中英混合 1 token ≈ 2.5 字符),只供量级感知。 @@ -856,34 +869,16 @@ function isToolResultJson(content: string | undefined | null): boolean { } } /// 展开的 JSON 折叠块 id 集合(点击切换);默认折叠(max-height 80px 裁剪,可滚动) -const expandedJsonIds = ref<Set<string>>(new Set()) -function toggleJson(id: string): void { - const next = new Set(expandedJsonIds.value) - if (next.has(id)) next.delete(id) - else next.add(id) - expandedJsonIds.value = next -} +const { ids: expandedJsonIds, toggle: toggleJson } = useToggleSet() // A 过程压缩:执行轨迹展开态 // 完成轮次的过程文本+工具卡默认折叠为一张轨迹卡;用户手动展开后记入此集合, // 不再被自动收回(换轮次/切会话回来都保持,会话切换时整体重置)。 -const expandedTraceIds = ref<Set<string>>(new Set()) -function toggleTrace(key: string): void { - const next = new Set(expandedTraceIds.value) - if (next.has(key)) next.delete(key) - else next.add(key) - expandedTraceIds.value = next -} +const { ids: expandedTraceIds, toggle: toggleTrace } = useToggleSet() // UX 密度改造:轨迹内每轮思考单行 pill(灯泡+单行截断+右端 token 小字),点击展开完整思考段落。 // 展开态按 msg.id 记忆,与轨迹卡展开态(expandedTraceIds)互不影响。 -const expandedThinkIds = ref<Set<string>>(new Set()) -function toggleThink(id: string): void { - const next = new Set(expandedThinkIds.value) - if (next.has(id)) next.delete(id) - else next.add(id) - expandedThinkIds.value = next -} +const { ids: expandedThinkIds, toggle: toggleThink } = useToggleSet() /** 轮次思考纯文本:reasoning_content 优先(DeepSeek/Anthropic thinking 同通道),content 兜底; * 压平 markdown 换行;两者皆空回退「过程记录」占位(纯工具轮也有锚点) */ @@ -1126,6 +1121,8 @@ function agentFinTone(status: AgentFinishedCard['status']): 'ok' | 'warn' | 'err // 未知值兜底中性 completed 词不误报。无字段(旧后端)回退 summary 启发式:用户停止 // 级联取消子代理时 stop_reason=「已被取消(...)」并经 summary 终止说明透出(后端两处 // 取消检查点均写此中文字面量),按该特征分流为「已取消」;无此特征才是系统截断。 +// A-3(2026-10-05):并行多子代理卡序号身份——从 agentId 尾段提 agent-{n} 序号, +// 并行交错完成时卡面可区分「哪张卡是哪个代理」,防「3 轮/2 轮」乱序观感误读。 function agentFinStatusText(card: AgentFinishedCard): string { const k = card.stop_kind if (k) { @@ -1145,6 +1142,13 @@ function agentFinStatusText(card: AgentFinishedCard): string { if (card.status === 'failed') return t('ai.agentFinished.failed') return t('ai.agentFinished.completed') } +// A-3(2026-10-05):并行多子代理卡序号身份——从 agentId 尾段提取 agent-{n} 的 n, +// 并行交错完成时卡面可区分「哪张卡是哪个代理」,防多卡「N 轮」乱序观感误读; +// 解析失败(形态不符)返回 null,模板不渲染序号徽标。 +function agentFinSeq(card: AgentFinishedCard): number | null { + const m = /::agent-(\d+)$/.exec(card.agentId || '') + return m ? Number(m[1]) : null +} // 启动失败型子代理(并发占满/父会话已删/provider 缺失)在进循环前就返回,子会话未落库, // 点卡 peek 必报「对话不存在」死胡同;rounds=0 是前端可得的最稳特征(进循环后的失败 // 也至少有一轮落库记录,可正常 peek)。 @@ -1311,7 +1315,19 @@ function jumpToTrace(key: string): void { } // 切换对话/新对话时由父取消编辑态;滚动边沿/跟随意图重置在此自管。 -watch(() => store.state.activeConversationId, () => { +// V12 per-conv 滚动位置记忆:换出会话存 scrollTop,换入会话有存档则恢复(不再一律回底)。 +// 容量收敛:仅记最近 20 个会话(Map 超限删最早),防长会话列表无限增长。 +const _scrollTopMemo = new Map<string, number>() +const _SCROLL_MEMO_LIMIT = 20 + +watch(() => store.state.activeConversationId, (newId, oldId) => { + // 换出:记旧会话滚动位置(容器就绪且旧 id 有效才记) + if (oldId && messagesContainer.value) { + if (_scrollTopMemo.size >= _SCROLL_MEMO_LIMIT) { + _scrollTopMemo.delete(_scrollTopMemo.keys().next().value!) + } + _scrollTopMemo.set(oldId, messagesContainer.value.scrollTop) + } // 切会话重置滚动边沿检测 + 跟随意图(新会话默认跟随底部) setFollowing(true) // 切会话重置执行轨迹展开态(展开记忆按会话隔离,防跨会话 key 撞车误展开) @@ -1334,7 +1350,18 @@ watch(() => store.state.activeConversationId, () => { // UX 走查-对话域 B2:切会话显式回底。仅 watch messages.length 时,两个等长消息数的会话 // 互切不触发任何滚动,scrollTop 残留旧会话位置(可能停在中段)。setFollowing(true)已声明 // 跟随意图,此处补上实际滚动;nextTick 等新会话消息渲染出真实 scrollHeight 再滚。 - nextTick(() => scrollToBottom()) + // V12(2026-10-05):有滚动位置存档的会话恢复存档位置(回读不被打断),无存档(首次/新会话) + // 保持回底。跟随意图仅在回底路径置位;恢复路径置 setFollowing(false) 尊重用户阅读位置。 + nextTick(() => { + const saved = newId ? _scrollTopMemo.get(newId) : undefined + const el = messagesContainer.value + if (saved != null && el) { + el.scrollTop = Math.min(saved, el.scrollHeight) + setFollowing(false) + } else { + scrollToBottom() + } + }) }) // 流式 rAF 清理:组件卸载时若仍有 pending rAF(streaming 中途切走/关窗),取消防泄漏。 @@ -1369,7 +1396,8 @@ defineExpose({ <template> <!-- Messages --> - <div class="ai-messages" ref="messagesContainer" @scroll="handleMessagesScroll" @click="onMdCodeCopyClick"> + <!-- UX第五轮 P2-G1:role="log"+aria-live=polite,读屏用户可感知流式新消息与回复更新 --> + <div class="ai-messages" ref="messagesContainer" role="log" aria-live="polite" @scroll="handleMessagesScroll" @click="onMdCodeCopyClick"> <!-- 空状态 (UX-2025-20: 示例问题卡片 + 无 provider 引导跳 Settings) — 提取至 EmptyState.vue --> <EmptyState v-if="store.state.messages.length === 0 && !store.state.streaming" @@ -1421,7 +1449,7 @@ defineExpose({ @click="toggleThink(m.id)" > <span class="ai-think-ic"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 00-4 12.7c.6.5 1 1.4 1 2.3h6c0-.9.4-1.8 1-2.3A7 7 0 0012 2z"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 00-4 12.7c.6.5 1 1.4 1 2.3h6c0-.9.4-1.8 1-2.3A7 7 0 0012 2z"/></svg> </span> <span class="ai-think-text">{{ thinkSnippet(m) }}</span> <span v-if="roundTokenText(m)" class="ai-think-tokens">{{ roundTokenText(m) }}</span> @@ -1465,6 +1493,8 @@ defineExpose({ @click="openAgentTrace(item.card)" > <span class="ai-agent-fin-dot"></span> + <!-- A-3 并行多子代理卡序号身份:agent-{n} 解析成功才渲染,防多卡轮次乱序误读 --> + <span v-if="agentFinSeq(item.card) !== null" class="ai-agent-fin-seq">#{{ agentFinSeq(item.card) }}</span> <span class="ai-agent-fin-label">{{ $t('ai.agentFinished.title') }}</span> <span class="ai-agent-fin-status">{{ agentFinStatusText(item.card) }}</span> <!-- 被拒工具 chip(P2-1):非空才渲染,琥珀警示层与 truncated 同色系; @@ -1509,7 +1539,7 @@ defineExpose({ :class="'ai-msg-segment--' + item.seg.kind" @click="toggleSegment(item.seg.key)" > - <svg class="ai-msg-segment-icon" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" class="ai-msg-segment-icon" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <template v-if="item.seg.kind === 'archived'"> <polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/> </template> @@ -1545,16 +1575,14 @@ defineExpose({ v-else-if="item.msg.role === 'user'" :msg="item.msg" :can-edit="isLastUser(item.msg) && !store.state.streaming && item.msg.id !== editingMsgId" - :can-regenerate="isLastUser(item.msg) && !store.state.streaming" @copy="copyMsgContent" @start-edit="(m) => emit('start-edit', m)" - @regenerate="handleRegenerate" /> <!-- AI 消息 --> <div v-else class="ai-msg-ai"> <div class="ai-msg-avatar ai-msg-avatar--ai"> - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/></svg> </div> <div class="ai-msg-content"> <!-- 文本内容(流式或固定,Markdown 渲染) --> @@ -1583,7 +1611,10 @@ defineExpose({ <div v-for="block in decoratedStreamingBlocks" :key="block.key" v-html="block.html"></div> <span v-if="isViewingGenerating" class="ai-cursor"></span> </template> - <!-- 完成/历史:整段v-html(不变,经装饰层注入代码块复制按钮) --> + <!-- 完成/历史:整段v-html(不变,经装饰层注入代码块复制按钮)。 + B5 惰性渲染:折叠态仅渲染前 800 字符预览(全量渲染延迟到展开瞬间), + 长(>1500字)历史消息的 markdown 渲染成本不再随会话长度线性累积。 --> + <div v-else-if="shouldCollapseLong(item.msg)" v-html="collapsedPreviewHtml(item.msg)"></div> <div v-else v-html="renderMdDecorated(item.msg.content)"></div> <!-- M1/R3:超长 assistant 文本折叠条(折叠态带量:已折叠 N 字符/约 M tokens) --> <button @@ -1597,7 +1628,7 @@ defineExpose({ :title="$t('aiChat.copyMsg')" @click.stop="copyMsgContent(item.msg)" > - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg> </button> <button v-if="item.msg.content" @@ -1605,7 +1636,7 @@ defineExpose({ :title="$t('aiChat.copyAsImage')" @click.stop="copyMsgAsImage(item.msg)" > - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> </button> </div> <!-- 流式开始但尚无文本时显示光标 --> @@ -1622,7 +1653,7 @@ defineExpose({ :disabled="store.state.streaming" @click.stop="handleRetry" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> <span>{{ $t('aiChat.retry') }}</span> </button> <button @@ -1630,7 +1661,7 @@ defineExpose({ class="ai-msg-act" @click.stop="emit('go-to-settings')" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> <span>{{ $t('aiChat.goToSettingsAction') }}</span> </button> </div> @@ -1687,6 +1718,23 @@ defineExpose({ <span class="ai-token-popover-label">{{ $t('aiChat.tokenReasonLabel') }}</span> <span class="ai-token-popover-val">{{ formatTokensK(tokenReasonOf(item.msg)) }} ({{ tokenReasonOf(item.msg) }})</span> </div> + <!-- #12(2026-10-05):会话累计(convTokenTotal)入面板——数据只写不读的闲置数据源接上消费; + 累计为前端内存态(切换会话由 switchConversation 重算),会话末条 AI 回复的 popover 展示 --> + <template v-if="isLastAi(item.msg) && store.state.convTokenTotal"> + <div class="ai-token-popover-sep"></div> + <div class="ai-token-popover-row ai-token-popover-row--total"> + <span class="ai-token-popover-label">{{ $t('aiChat.tokenConvTotal') }}</span> + <span class="ai-token-popover-val">{{ formatTokensK(store.state.convTokenTotal.total) }} ({{ store.state.convTokenTotal.total }})</span> + </div> + <div class="ai-token-popover-row"> + <span class="ai-token-popover-label">{{ $t('aiChat.tokenConvTotalIn') }}</span> + <span class="ai-token-popover-val">{{ formatTokensK(store.state.convTokenTotal.prompt) }} ({{ store.state.convTokenTotal.prompt }})</span> + </div> + <div class="ai-token-popover-row"> + <span class="ai-token-popover-label">{{ $t('aiChat.tokenConvTotalOut') }}</span> + <span class="ai-token-popover-val">{{ formatTokensK(store.state.convTokenTotal.completion) }} ({{ store.state.convTokenTotal.completion }})</span> + </div> + </template> <!-- 命中率与 cache 段同闸:无缓存口径的 provider 不显 0%,防「无数据」误读成「全未命中」 --> <div v-if="tokenCacheVisible(item.msg) && cacheHitRate(item.msg) != null" class="ai-token-popover-row"> <span class="ai-token-popover-label">{{ $t('aiChat.tokenCacheRateLabel') }}</span> @@ -1723,9 +1771,11 @@ defineExpose({ </div> <!-- 回到底部(上滑看历史 / 有新内容时显示) --> + <!-- R11(2026-10-05):生成中且非跟随态 → 按钮加脉冲点,提示「下面有新内容」而非仅导航 --> <Transition name="back-to-bottom"> - <button v-if="showBackToBottom" class="ai-back-to-bottom" @click="() => scrollToBottom()" :title="$t('aiChat.backToBottom')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg> + <button v-if="showBackToBottom" class="ai-back-to-bottom" :class="{ 'has-new': store.state.streaming && !isFollowingBottom }" @click="() => scrollToBottom()" :title="$t('aiChat.backToBottom')"> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg> + <span v-if="store.state.streaming && !isFollowingBottom" class="ai-btb-new-dot"></span> </button> </Transition> @@ -1744,7 +1794,7 @@ defineExpose({ </div> </div> <button class="ai-agent-peek-close" :title="$t('common.close')" @click="closeAgentTrace"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> </button> </div> <div class="ai-agent-peek-body"> @@ -1809,6 +1859,21 @@ defineExpose({ .back-to-bottom-leave-active { transition: opacity 0.15s, transform 0.15s; } .back-to-bottom-enter-from, .back-to-bottom-leave-to { opacity: 0; transform: translateY(8px); } +/* R11 新内容脉冲点:生成中非跟随态提示下方有新内容(amber 脉冲,2s 循环) */ +.ai-btb-new-dot { + position: absolute; + top: -2px; + right: -2px; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--df-warning, #e8a33d); + animation: ai-btb-pulse 2s ease-in-out infinite; +} +@keyframes ai-btb-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.45; transform: scale(1.25); } +} /* -- Empty State 样式已随 EmptyState.vue 提取 -- */ @@ -2285,6 +2350,15 @@ defineExpose({ gap: 12px; padding: 2px 0; } +/* #12 会话累计段分隔线 + 总计行加重 */ +.ai-token-popover-sep { + height: 0.5px; + background: var(--df-border); + margin: 4px 0; +} +.ai-token-popover-row--total .ai-token-popover-label { + font-weight: 600; +} .ai-token-popover-label { color: var(--df-text-dim); } @@ -2615,6 +2689,13 @@ defineExpose({ flex-shrink: 0; font-weight: 600; } +/* A-3 并行多子代理卡序号徽标:等宽小号弱化展示,解析失败不渲染(v-if 已守) */ +.ai-agent-fin-seq { + flex-shrink: 0; + font-family: var(--df-font-mono, monospace); + font-size: 11px; + opacity: 0.65; +} .ai-agent-fin-status { flex-shrink: 0; } diff --git a/src/components/ai/TaskBoundaryCard.vue b/src/components/ai/TaskBoundaryCard.vue index 71f9712..8ef6bce 100644 --- a/src/components/ai/TaskBoundaryCard.vue +++ b/src/components/ai/TaskBoundaryCard.vue @@ -5,14 +5,16 @@ (中途改边界=边跑边扩权,设计明令禁止)。不引入对话框,最小折叠卡。 --> <div class="ai-boundary"> <button type="button" class="ai-boundary-toggle" @click="toggleOpen"> - <svg class="ai-boundary-chevron" :class="{ 'is-open': open }" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> + <svg aria-hidden="true" class="ai-boundary-chevron" :class="{ 'is-open': open }" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> <span class="ai-boundary-toggle-label">{{ t('ai.taskBoundary.toggle') }}</span> <span v-if="current" class="ai-boundary-echo">{{ echoSummary }}</span> </button> <div v-if="open" class="ai-boundary-body"> <div v-if="locked" class="ai-boundary-locked">{{ t('ai.taskBoundary.lockedGenerating') }}</div> + <!-- A11: 卡展开切会话保留未提交编辑时的可见提示(编辑不静默蒸发,但目标会话已变) --> + <div v-if="open && hasDirtyEdits" class="ai-boundary-dirty">{{ t('ai.taskBoundary.dirtyKept') }}</div> <!-- 拉取失败 ≠ 无边界:失败态禁「一次授权」(防默认空表单覆盖原授权),保留表单现状供重试 --> <div v-if="loadFailed" class="ai-boundary-loadfail"> @@ -61,29 +63,30 @@ </div> <button type="button" class="ai-boundary-budget-toggle" @click="budgetOpen = !budgetOpen"> - <svg class="ai-boundary-chevron" :class="{ 'is-open': budgetOpen }" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> + <svg aria-hidden="true" class="ai-boundary-chevron" :class="{ 'is-open': budgetOpen }" width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> {{ t('ai.taskBoundary.budgetToggle') }} </button> <div v-if="budgetOpen" class="ai-boundary-budget"> - <label class="ai-boundary-field"> + <label class="ai-boundary-field" :class="{ 'is-error': budgetErrField === 'rounds' }"> <span>{{ t('ai.taskBoundary.budgetRounds') }}</span> - <input v-model="rounds" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" /> + <input v-model="rounds" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" @input="budgetErrField = ''" /> </label> - <label class="ai-boundary-field"> + <label class="ai-boundary-field" :class="{ 'is-error': budgetErrField === 'tokens' }"> <span>{{ t('ai.taskBoundary.budgetTokens') }}</span> - <input v-model="tokens" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" /> + <input v-model="tokens" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" @input="budgetErrField = ''" /> </label> - <label class="ai-boundary-field"> + <label class="ai-boundary-field" :class="{ 'is-error': budgetErrField === 'wallSecs' }"> <span>{{ t('ai.taskBoundary.budgetWallSecs') }}</span> - <input v-model="wallSecs" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" /> + <input v-model="wallSecs" class="ai-boundary-input ai-boundary-input--num" type="text" inputmode="numeric" :disabled="locked" @input="budgetErrField = ''" /> </label> <span class="ai-boundary-dim">{{ t('ai.taskBoundary.budgetHint') }}</span> </div> <div class="ai-boundary-actions"> - <button type="button" class="ai-boundary-confirm" :disabled="locked || loadFailed" @click="confirmSet">{{ t('ai.taskBoundary.confirmSet') }}</button> + <!-- A12: confirm/clear in-flight 防重(busy 禁用,防双击双 IPC 后到覆盖先到) --> + <button type="button" class="ai-boundary-confirm" :disabled="locked || loadFailed || confirming" @click="confirmSet">{{ t('ai.taskBoundary.confirmSet') }}</button> <!-- A3: 清除=收权(安全向),generating 中保持可用,与字段编辑锁分离;有 confirm 二次确认 --> - <button v-if="current" type="button" class="ai-boundary-clear" @click="clearBoundary">{{ t('ai.taskBoundary.clearBoundary') }}</button> + <button v-if="current" type="button" class="ai-boundary-clear" :disabled="confirming" @click="clearBoundary">{{ t('ai.taskBoundary.clearBoundary') }}</button> </div> </div> @@ -125,6 +128,24 @@ const budgetOpen = ref(false) const rounds = ref('') const tokens = ref('') const wallSecs = ref('') +// A12: in-flight 防重(双击双 IPC 后到覆盖先到)+ 出错字段定位 +const confirming = ref(false) +const budgetErrField = ref<'' | 'rounds' | 'tokens' | 'wallSecs'>('') +// A11: 未提交脏编辑保护(切会话不静默蒸发正在输入的目录/预算) +const hasDirtyEdits = computed(() => { + if (!current.value) { + return dirs.value.length > 0 || dirInput.value.trim() !== '' + || rounds.value.trim() !== '' || tokens.value.trim() !== '' || wallSecs.value.trim() !== '' + } + const b = current.value + const dirsDirty = JSON.stringify(dirs.value) !== JSON.stringify([...b.dir_allowlist]) + const budgetDirty = (b.budget + ? rounds.value.trim() !== String(b.budget.max_rounds) + || tokens.value.trim() !== String(b.budget.max_tokens) + || wallSecs.value.trim() !== String(b.budget.wall_secs) + : rounds.value.trim() !== '' || tokens.value.trim() !== '' || wallSecs.value.trim() !== '') + return dirsDirty || budgetDirty +}) const activeId = computed(() => store.state.activeConversationId) // 生成中锁编辑:防「边跑边扩权」。isGenerating 派生自 convStates reactive Map,computed 内可追踪 @@ -183,11 +204,12 @@ function toggleOpen(): void { if (open.value) void hydrate() } -// 切会话:预拉边界喂侧栏徽标镜像;展开态走 hydrate(内含拉取)重填表单,防上一会话表单串台 +// 切会话:预拉边界喂侧栏徽标镜像;展开态走 hydrate(内含拉取)重填表单,防上一会话表单串台。 +// A11: 卡展开且有未提交脏编辑时不静默重填(保留编辑至用户手动收起/确认),仅静默拉取喂镜像。 watch(activeId, (id) => { if (!id) return - if (open.value) void hydrate() - else void fetchConvBoundary(id) + void fetchConvBoundary(id) + if (open.value && !hasDirtyEdits.value) void hydrate() }) onMounted(() => { if (activeId.value) void fetchConvBoundary(activeId.value) @@ -235,12 +257,12 @@ function removeDir(d: string): void { dirs.value = dirs.value.filter(x => x !== d) } -/** 预算三项解析:全空=undefined(不限);任一非空但非正整数视为无效返回 null */ +/** 预算三项解析:全空=undefined(不限);任一非空但非正整数视为无效返回 null(带字段键定位) */ function parseBudget(): { max_rounds: number; max_tokens: number; wall_secs: number } | undefined | null { const fields = [ - { v: rounds.value.trim(), min: 1 }, - { v: tokens.value.trim(), min: 1 }, - { v: wallSecs.value.trim(), min: 1 }, + { v: rounds.value.trim(), key: 'rounds' as const }, + { v: tokens.value.trim(), key: 'tokens' as const }, + { v: wallSecs.value.trim(), key: 'wallSecs' as const }, ] if (fields.every(f => !f.v)) return undefined const nums: number[] = [] @@ -250,7 +272,11 @@ function parseBudget(): { max_rounds: number; max_tokens: number; wall_secs: num continue } const n = Number(f.v) - if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) return null + if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) { + // A12: 记录出错字段,toast 定位提示(替代笼统「预算需为正整数」) + budgetErrField.value = f.key + return null + } nums.push(n) } return { max_rounds: nums[0], max_tokens: nums[1], wall_secs: nums[2] } @@ -259,10 +285,16 @@ function parseBudget(): { max_rounds: number; max_tokens: number; wall_secs: num /** 一次授权:set 当前活跃会话边界并收卡。未落库会话后端内存即生效,首条消息落库随行 */ async function confirmSet(): Promise<void> { const id = activeId.value - if (!id || locked.value || loadFailed.value) return + if (!id || locked.value || loadFailed.value || confirming.value) return const budget = parseBudget() if (budget === null) { - emit('toast', { msg: t('ai.taskBoundary.budgetInvalid'), type: 'warning' }) + // A12: 出错字段定位(红字标到具体输入框 + toast 指名) + const fieldLabel = budgetErrField.value === 'rounds' + ? t('ai.taskBoundary.budgetRounds') + : budgetErrField.value === 'tokens' + ? t('ai.taskBoundary.budgetTokens') + : t('ai.taskBoundary.budgetWallSecs') + emit('toast', { msg: t('ai.taskBoundary.budgetInvalidField', { field: fieldLabel }), type: 'warning' }) return } const boundary: TaskBoundary = { @@ -271,6 +303,7 @@ async function confirmSet(): Promise<void> { forbid_destructive: true, budget: budget ?? null, } + confirming.value = true try { await saveConvBoundary(id, boundary) open.value = false @@ -278,6 +311,9 @@ async function confirmSet(): Promise<void> { } catch (e) { const msg = e instanceof Error ? e.message : String(e) emit('toast', { msg: t('ai.taskBoundary.setFail', { msg }), type: 'error' }) + } finally { + // A12: 防重守卫复位(成功收卡/失败停留都复位;3s 兜底防 IPC 悬挂卡死) + confirming.value = false } } @@ -286,8 +322,9 @@ async function confirmSet(): Promise<void> { * 不该是停掉整个任务);因运行中收权影响后续工具审批方式,加 confirm 二次确认 */ async function clearBoundary(): Promise<void> { const id = activeId.value - if (!id) return + if (!id || confirming.value) return if (!await confirmDialog(t('ai.taskBoundary.clearConfirm'))) return + confirming.value = true try { await saveConvBoundary(id, null) resetForm() @@ -295,6 +332,8 @@ async function clearBoundary(): Promise<void> { } catch (e) { const msg = e instanceof Error ? e.message : String(e) emit('toast', { msg: t('ai.taskBoundary.setFail', { msg }), type: 'error' }) + } finally { + confirming.value = false } } @@ -525,6 +564,14 @@ const echoSummary = computed(() => { cursor: pointer; border-radius: calc(var(--df-radius) - 2px); } -.ai-boundary-clear:hover:not(:disabled) { color: var(--df-danger); border-color: var(--df-danger); } -.ai-boundary-clear:disabled { cursor: not-allowed; opacity: 0.5; } -</style> + .ai-boundary-clear:hover:not(:disabled) { color: var(--df-danger); border-color: var(--df-danger); } + .ai-boundary-clear:disabled { cursor: not-allowed; opacity: 0.5; } + /* A12: 预算字段校验错误着色(定位到具体输入框,替代笼统 toast) */ + .ai-boundary-field.is-error .ai-boundary-input { border-color: var(--df-danger); } + .ai-boundary-field.is-error span { color: var(--df-danger); } + /* A11: 脏编辑提示条(切会话保留编辑时的可见说明) */ + .ai-boundary-dirty { + font-size: 10.5px; + color: var(--df-warning); + } + </style> diff --git a/src/components/ai/TopBar.vue b/src/components/ai/TopBar.vue index a61e0e9..78a4ba5 100644 --- a/src/components/ai/TopBar.vue +++ b/src/components/ai/TopBar.vue @@ -5,24 +5,26 @@ <div class="ai-header"> <div class="ai-header-left"> <button class="ai-btn-icon" @click="emit('toggle-sidebar')" :title="$t('aiChat.conversationList')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg> </button> <span class="ai-header-icon"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/><line x1="9" y1="21" x2="15" y2="21"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1H9a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/><line x1="9" y1="21" x2="15" y2="21"/></svg> </span> <span class="ai-header-title">{{ $t('ai.assistant') }}</span> - <div + <button v-if="pendingApprovalCount > 0" + type="button" class="ai-approval-badge" :title="$t('aiChat.pendingApprovalHint')" @click="emit('scroll-to-pending')" > ⏳ {{ $t('aiChat.pendingApprovalCount', { n: pendingApprovalCount }) }} - </div> + </button> </div> <!-- 提供商状态 + 模型选择器 --> <div class="ai-header-center"> - <div + <button + type="button" class="ai-provider-bar" :class="{ 'ai-provider-bar--switchable': store.state.providers.length > 1 }" v-if="store.state.providers.length > 0" @@ -31,7 +33,7 @@ > <span class="provider-dot" :class="'provider-dot--' + providerStatus"></span> <span class="provider-name">{{ activeProviderName }}</span> - </div> + </button> <div class="ai-provider-bar ai-provider-empty" v-else> <span class="provider-hint">{{ $t('aiChat.providerNotConfigured') }}</span> </div> @@ -60,7 +62,7 @@ :title="$t('aiChat.moreActions')" @click="moreMenuOpen = !moreMenuOpen" > - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg> </button> <Transition name="ai-more-popout"> <div v-if="moreMenuOpen" class="ai-more-popout" role="menu"> @@ -70,7 +72,7 @@ :disabled="!hasActiveMessages || store.state.streaming" @click="emit('clear-context'); moreMenuOpen = false" > - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg> <span>{{ $t('aiChat.clearContext') }}</span> </button> <!-- F-15 阶段2: 压缩上下文(LLM 摘要落 system + 历史消息标 compressed 归档) --> @@ -79,10 +81,18 @@ :disabled="!hasActiveMessages || store.state.streaming || store.isCompressing.value" @click="emit('compress-context'); moreMenuOpen = false" > - <svg v-if="!store.isCompressing.value" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg> - <svg v-else class="ai-btn-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> + <svg aria-hidden="true" v-if="!store.isCompressing.value" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg> + <svg aria-hidden="true" v-else class="ai-btn-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> <span>{{ store.isCompressing.value ? $t('aiChat.compressing') : $t('aiChat.compressContext') }}</span> </button> + <!-- I12(2026-10-05):快捷键速查入口(零快捷键提示补课;面板体在 AiChat Teleport 渲染) --> + <button + class="ai-more-item" + @click="emit('show-shortcuts'); moreMenuOpen = false" + > + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><line x1="6" y1="10" x2="6.01" y2="10"/><line x1="10" y1="10" x2="10.01" y2="10"/><line x1="14" y1="10" x2="14.01" y2="10"/><line x1="18" y1="10" x2="18.01" y2="10"/><line x1="7" y1="14" x2="7.01" y2="14"/><line x1="11" y1="14" x2="11.01" y2="14"/><line x1="15" y1="14" x2="15.01" y2="14"/></svg> + <span>{{ $t('aiChat.shortcutsTitle') }}</span> + </button> </div> </Transition> </div> @@ -90,26 +100,31 @@ <!-- 嵌入模式:最大化/还原 + 分离 + 关闭 --> <template v-if="!detached"> <button class="ai-btn-icon" @click="emit('toggle-maximize')" :title="store.state.maximized ? $t('aiChat.restoreSidebar') : $t('aiChat.maximize')"> - <svg v-if="store.state.maximized" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg> - <svg v-else width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/></svg> + <svg aria-hidden="true" v-if="store.state.maximized" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg> + <svg aria-hidden="true" v-else width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/></svg> </button> <button class="ai-btn-icon" @click="emit('detach')" :title="$t('aiChat.detachWindow')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg> </button> <button class="ai-btn-icon" @click="emit('close-panel')" :title="$t('aiChat.closePanel')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> </button> </template> - <!-- 分离模式:吸附 + 置顶 + 关闭 --> + <!-- 分离模式:收回主面板 + 吸附 + 置顶 + 关闭 --> <template v-else> + <!-- V11b(2026-10-05):收回分离窗回主面板(emitReattachToMain → 主窗 reattachPanel), + 分离窗自关。与「关闭」区别:关闭仅关窗不恢复主面板,收回=分离→主面板合一 --> + <button class="ai-btn-icon" @click="emit('reattach')" :title="$t('aiChat.reattachToMain')"> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/><path d="M9 12h6"/><path d="M12 9l0 6"/></svg> + </button> <button class="ai-btn-icon" :class="{ 'ai-btn-icon--active': store.state.docked }" @click="emit('dock')" :title="store.state.docked ? $t('aiChat.undock') : $t('aiChat.dockRight')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="8" height="18" rx="1"/><rect x="14" y="3" width="8" height="18" rx="1"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="8" height="18" rx="1"/><rect x="14" y="3" width="8" height="18" rx="1"/></svg> </button> <button class="ai-btn-icon" :class="{ 'ai-btn-icon--active': alwaysOnTop }" @click="emit('toggle-always-on-top')" :title="alwaysOnTop ? $t('aiChat.unpin') : $t('aiChat.pinOnTop')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8 2 6 5 6 8v3l-2 2v1h16v-1l-2-2V8c0-3-2-6-6-6z"/><line x1="12" y1="14" x2="12" y2="22"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8 2 6 5 6 8v3l-2 2v1h16v-1l-2-2V8c0-3-2-6-6-6z"/><line x1="12" y1="14" x2="12" y2="22"/></svg> </button> <button class="ai-btn-icon" @click="emit('close-detached')" :title="$t('aiChat.closeWindow')"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> </button> </template> </div> @@ -121,8 +136,11 @@ <div class="ai-bottom-tools-right"> <!-- 无人值守任务进度(AiTaskProgress,设计 §4.3):当前会话有进度快照时显示, 静默期(子代理长跑/大工具执行/重入退避)凭最后快照可见「在干活」;终局即清。 - max_rounds=0(不限轮次)不显分母进度条,只显轮次与 token。 --> - <div v-if="taskProgress" class="ai-task-progress"> + max_rounds=0(不限轮次)不显分母进度条,只显轮次与 token。 + A8 双进度矛盾:输入区 agentic 实时进度条可见时(streaming 且 agentRound>0) + 隐藏本快照徽章——两处同屏且轮数口径不同(实时 vs 每5轮快照)互相矛盾; + 快照徽章只服务输入区不可见场景(托盘/单轮大工具静默期)。 --> + <div v-if="taskProgress && !agenticProgressVisible" class="ai-task-progress"> <span class="ai-task-progress-dot"></span> <span class="ai-task-progress-text">{{ taskProgressText }}</span> <span @@ -135,10 +153,10 @@ </div> <!-- 目标计数 --> <div v-if="goals.length" class="ai-goals-inline"> - <span class="ai-goals-inline-badge ai-tool-badge" :class="{ 'is-active': goalsExpanded }" @click="togglePanel('goals')" :title="$t('aiChat.viewGoals')"> - <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg> + <button type="button" class="ai-goals-inline-badge ai-tool-badge" :class="{ 'is-active': goalsExpanded }" @click="togglePanel('goals')" :title="$t('aiChat.viewGoals')"> + <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg> {{ goals.filter(g => g.status === 'active').length }}/{{ goals.length }} - </span> + </button> <Transition name="ai-pop"> <div v-if="goalsExpanded" class="ai-goals-inline-list"> <div v-for="(g, i) in goals" :key="i" class="ai-goal-inline-item" :class="{ 'ai-goal-completed': g.status === 'completed' }"> @@ -152,10 +170,10 @@ </div> <!-- 历史消息 --> <div v-if="historyMsgs.length" class="ai-history-inline"> - <span class="ai-history-inline-badge ai-tool-badge" :class="{ 'is-active': historyExpanded }" @click="togglePanel('history')" :title="$t('aiChat.viewHistory')"> - <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> + <button type="button" class="ai-history-inline-badge ai-tool-badge" :class="{ 'is-active': historyExpanded }" @click="togglePanel('history')" :title="$t('aiChat.viewHistory')"> + <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> {{ historyMsgs.length }} - </span> + </button> <Transition name="ai-pop"> <div v-if="historyExpanded" class="ai-history-inline-list"> <div v-for="h in historyMsgs" :key="h.id" class="ai-history-inline-item" @click="scrollToMessage(h.id)"> @@ -168,10 +186,10 @@ </div> <!-- 上下文摘要 --> <div v-if="summaryMsgs.length" class="ai-summary-inline"> - <span class="ai-summary-inline-badge ai-tool-badge" :class="{ 'is-active': summaryExpanded }" @click="togglePanel('summary')" :title="$t('aiChat.viewSummary')"> - <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h12"/></svg> + <button type="button" class="ai-summary-inline-badge ai-tool-badge" :class="{ 'is-active': summaryExpanded }" @click="togglePanel('summary')" :title="$t('aiChat.viewSummary')"> + <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h12"/></svg> {{ summaryMsgs.length }} - </span> + </button> <Transition name="ai-pop"> <div v-if="summaryExpanded" class="ai-summary-inline-list"> <div v-for="(s, i) in summaryMsgs" :key="i" class="ai-summary-inline-item" @click="scrollToMessage(s.id)"> @@ -183,9 +201,9 @@ </div> <!-- L3 完整上下文 --> <div v-if="contextInfo.goals.length || contextInfo.summaryCount > 0" class="ai-context-inline"> - <span class="ai-context-inline-badge ai-tool-badge" :class="{ 'is-active': contextExpanded }" @click="togglePanel('context')" :title="$t('aiChat.viewContext')"> - <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><circle cx="12" cy="8" r="1" fill="currentColor"/></svg> - </span> + <button type="button" class="ai-context-inline-badge ai-tool-badge" :class="{ 'is-active': contextExpanded }" @click="togglePanel('context')" :title="$t('aiChat.viewContext')"> + <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><circle cx="12" cy="8" r="1" fill="currentColor"/></svg> + </button> <Transition name="ai-pop"> <div v-if="contextExpanded" class="ai-context-inline-list"> <!-- 系统提示 --> @@ -276,8 +294,12 @@ const emit = defineEmits<{ (e: 'dock'): void /** 置顶切换(父 toggleAlwaysOnTop 调 Tauri setAlwaysOnTop) */ (e: 'toggle-always-on-top'): void + /** V11b 收回分离窗回主面板(父 emitReattachToMain 广播,主窗 reattachPanel 执行) */ + (e: 'reattach'): void /** 关闭分离窗口(store.closeDetachedWindow) */ (e: 'close-detached'): void + /** I12 展示快捷键速查面板(父 shortcutsVisible=true,面板体在父 Teleport 到 body) */ + (e: 'show-shortcuts'): void /** Provider 循环切换后回传 model 名(父 showToast 弹切换提示,保持单一 toast 源) */ (e: 'provider-switched', modelName: string): void }>() @@ -387,6 +409,11 @@ const taskProgress = computed(() => { if (!id) return null return pendingTaskProgress.get(id) ?? null }) +// A8 双进度矛盾:输入区 agentic 实时进度条可见条件(与 AiChat.vue showAgenticProgress 同口径)—— +// 可见时 TopBar 快照徽章让位隐藏(轮数口径不同:实时 vs 每5轮快照,同屏互相矛盾) +const agenticProgressVisible = computed(() => + store.state.streaming && store.state.agentRound > 0, +) const taskProgressText = computed(() => { const p = taskProgress.value if (!p) return '' @@ -654,7 +681,7 @@ onBeforeUnmount(() => { white-space: nowrap; max-width: 200px; } -/* 审批徽标(待审批>0 时显于头部,点击跳首个审批卡) */ +/* 审批徽标(待审批>0 时显于头部,点击跳首个审批卡;div→button 后归零默认样式) */ .ai-approval-badge { display: inline-flex; align-items: center; @@ -667,10 +694,18 @@ onBeforeUnmount(() => { font-weight: 500; white-space: nowrap; transition: opacity 0.15s var(--df-ease); + font-family: inherit; + appearance: none; + /* review ③:div→button 后 UA outset 边框需显式归零(appearance:none 不含 border) */ + border: none; } .ai-approval-badge:hover { opacity: 0.85; } +.ai-approval-badge:focus-visible { + outline: 1.5px solid var(--df-accent, #4a9eff); + outline-offset: 1px; +} /* ═══ 模型选择器(对话透明化 L1:紧凑下拉) ═══ */ .ai-model-picker { @@ -840,7 +875,7 @@ onBeforeUnmount(() => { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } } -/* ═══ Provider Bar (header 中间区) ═══ */ +/* ═══ Provider Bar (header 中间区;div→button 后归零默认样式) ═══ */ .ai-provider-bar { display: inline-flex; align-items: center; @@ -848,6 +883,11 @@ onBeforeUnmount(() => { font-size: 11px; color: var(--df-text-secondary); white-space: nowrap; + font-family: inherit; + appearance: none; + background: none; + border: none; + padding: 0; } .ai-provider-bar--switchable { cursor: pointer; @@ -855,6 +895,11 @@ onBeforeUnmount(() => { .ai-provider-bar--switchable:hover { color: var(--df-text-primary); } +.ai-provider-bar--switchable:focus-visible { + outline: 1.5px solid var(--df-accent, #4a9eff); + outline-offset: 2px; + border-radius: 2px; +} /* provider 连通状态点:灰=未测/绿=最近成功/红=最近失败(基于最后 assistant 消息 isError) */ .provider-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--df-text-dim); flex-shrink: 0; transition: background .2s; } .provider-dot--ok { background: var(--df-success); } @@ -889,6 +934,14 @@ onBeforeUnmount(() => { border: 0.5px solid var(--df-border); transition: color 0.15s, background 0.15s; white-space: nowrap; + /* UX第五轮 P2-T1:span→button 键盘可达后,归零 button 默认样式保持原视觉 */ + font-family: inherit; + font-weight: normal; + appearance: none; +} +.ai-tool-badge:focus-visible { + outline: 1.5px solid var(--df-accent, #4a9eff); + outline-offset: 1px; } .ai-tool-badge svg { vertical-align: middle; } /* 图标在行高内垂直居中(与数字同基线)*/ .ai-tool-badge:hover { diff --git a/src/components/dashboard/ActiveProjectsPanel.vue b/src/components/dashboard/ActiveProjectsPanel.vue index a26889a..c450618 100644 --- a/src/components/dashboard/ActiveProjectsPanel.vue +++ b/src/components/dashboard/ActiveProjectsPanel.vue @@ -22,7 +22,7 @@ </div> <div class="project-meta-row"> <span class="project-meta-item"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3v12"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 01-9 9"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3v12"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 01-9 9"/></svg> {{ $t('dashboard.taskUnit', { n: p.activeTasks }) }} </span> <span class="project-meta-time">{{ p.lastActivity }}</span> diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue index 6e9ec24..1ee1b8a 100644 --- a/src/components/layout/AppSidebar.vue +++ b/src/components/layout/AppSidebar.vue @@ -3,7 +3,7 @@ <!-- Logo(7b:brand 区只保留品牌标识,收缩按钮已移到底部操作区) --> <div class="sidebar-brand"> <div class="brand-mark"> - <svg width="20" height="20" viewBox="0 0 24 24" fill="none"> + <svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none"> <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="url(#brand-grad)" /> <defs><linearGradient id="brand-grad" x1="3" y1="2" x2="21" y2="22"> <stop stop-color="#7b6ff0"/><stop offset="1" stop-color="#5eaff0"/> @@ -54,7 +54,7 @@ <!-- AI 面板切换按钮(footer 常驻跨路由渲染,承担后台运行中会话的全局可见标识) --> <button class="nav-link ai-toggle-btn" :class="{ 'ai-toggle-btn--active': aiStore.state.panelOpen }" @click="aiStore.togglePanel()" :title="runningConvCount > 0 ? `${runningConvCount} 个会话运行中` : (collapsed ? $t('nav.aiPanel') : undefined)"> <span class="nav-link-icon ai-spark-icon"> - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 3v3M21 4.5h-3M5 17v3M6.5 18.5h-3"/></svg> + <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 3v3M21 4.5h-3M5 17v3M6.5 18.5h-3"/></svg> </span> <span v-if="!collapsed" class="nav-link-text">{{ $t('nav.aiPanel') }}</span> <span v-if="!collapsed && runningConvCount > 0" class="nav-link-badge">{{ runningConvCount }}</span> @@ -63,15 +63,15 @@ </button> <router-link to="/settings" class="nav-link" active-class="nav-link--active" :title="collapsed ? $t('nav.settings') : undefined"> <span class="nav-link-icon"> - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> + <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg> </span> <span v-if="!collapsed" class="nav-link-text">{{ $t('nav.settings') }}</span> </router-link> <!-- 7b:收缩/展开切换按钮(最底部,快捷键 Ctrl+B 同效) --> <button class="nav-link collapse-toggle-btn" @click="toggleCollapsed" :title="collapsed ? $t('nav.expand') : $t('nav.collapse')"> <span class="nav-link-icon"> - <svg v-if="!collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> - <svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> + <svg aria-hidden="true" v-if="!collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg> + <svg aria-hidden="true" v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg> </span> <span v-if="!collapsed" class="nav-link-text">{{ $t('nav.collapse') }}</span> <span v-if="!collapsed" class="ai-toggle-shortcut">Ctrl+B</span> @@ -151,20 +151,20 @@ const runningConvCount = computed(() => { const primaryNav = [ { path: '/dashboard', label: 'nav.overview', badge: null, - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>' }, { path: '/ideas', label: 'nav.ideas', badge: null, - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6M10 22h4M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1h-6a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6M10 22h4M12 2a7 7 0 014 12.74V17a1 1 0 01-1 1h-6a1 1 0 01-1-1v-2.26A7 7 0 0112 2z"/></svg>' }, { path: '/projects', label: 'nav.projects', badge: null, - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>' }, { path: '/tasks', label: 'nav.tasks', badge: null, - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>' }, ] const secondaryNav = [ { path: '/knowledge', label: 'nav.knowledge', - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>' }, { path: '/audit', label: 'nav.audit', - svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' }, + svg: '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' }, ] </script> diff --git a/src/components/project/DependencyGraph.vue b/src/components/project/DependencyGraph.vue index c6594ec..36b0b86 100644 --- a/src/components/project/DependencyGraph.vue +++ b/src/components/project/DependencyGraph.vue @@ -30,7 +30,7 @@ type="button" :title="$t('fileExplorer.deleteCurrentModule')" > - <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> </button> <!-- 添加依赖弹窗 --> diff --git a/src/components/project/FileExplorer.vue b/src/components/project/FileExplorer.vue index 71064d2..709a15e 100644 --- a/src/components/project/FileExplorer.vue +++ b/src/components/project/FileExplorer.vue @@ -46,20 +46,20 @@ <div class="toolbar-right"> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.editCurrentModule')" @click="openEditModule"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> </button> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.deleteCurrentModule')" @click="confirmRemoveModule"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> </button> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.addModuleTitle')" @click="openAddModule"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> </button> <button class="btn btn-ghost btn-sm" type="button" :disabled="refreshing" @click="refresh"> <span v-if="refreshing" class="spinner"></span> {{ refreshing ? $t('fileExplorer.refreshing') : $t('fileExplorer.refresh') }} </button> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.detachToWindow')" @click="onDetach"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M15 3h6v6"/> <path d="M10 14L21 3"/> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/> @@ -74,10 +74,10 @@ <div class="explorer-sidebar"> <div class="explorer-view-tabs"> <button class="view-tab" :class="{ active: viewMode === 'tree' }" @click="viewMode = 'tree'"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> </button> <button class="view-tab" :class="{ active: viewMode === 'changes' }" @click="switchToChanges"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg> <span v-if="changedCount > 0" class="changed-badge">{{ changedCount }}</span> </button> </div> diff --git a/src/components/project/FileTree.vue b/src/components/project/FileTree.vue index 7aa4f73..1004957 100644 --- a/src/components/project/FileTree.vue +++ b/src/components/project/FileTree.vue @@ -233,78 +233,78 @@ function gitStatusLabel(status: string): string { /** 文件后缀 → SVG 品牌图标映射(simple-icons 品牌色,纯文本安全注入 v-html)。 */ /** 文件后缀 → SVG 品牌图标映射(simple-icons 品牌色,适配深浅主题)。 */ const FILE_ICONS: Record<string, string> = { - go: '<svg fill="#00ADD8" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z"/></svg>', - python: '<svg fill="#3776AB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>', - py: '<svg fill="#3776AB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>', - rs: '<svg fill="#DE5842" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z"/></svg>', - rust: '<svg fill="#DE5842" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z"/></svg>', - ts: '<svg fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', - tsx: '<svg fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', - mts: '<svg fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', - cts: '<svg fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', - js: '<svg fill="#F7DF1E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>', - jsx: '<svg fill="#F7DF1E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>', - vue: '<svg fill="#4FC08D" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z"/></svg>', - css: '<svg fill="#663399" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63"/></svg>', - scss: '<svg fill="#CC6699" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0zM9.615 15.998c.175.645.156 1.248-.024 1.792l-.065.18c-.024.061-.052.12-.078.176-.14.29-.326.56-.555.81-.698.759-1.672 1.047-2.09.805-.45-.262-.226-1.335.584-2.19.871-.918 2.12-1.509 2.12-1.509v-.003l.108-.061zm9.911-10.861c-.542-2.133-4.077-2.834-7.422-1.645-1.989.707-4.144 1.818-5.693 3.267C4.568 8.48 4.275 9.98 4.396 10.607c.427 2.211 3.457 3.657 4.703 4.73v.006c-.367.18-3.056 1.529-3.686 2.925-.675 1.47.105 2.521.615 2.655 1.575.436 3.195-.36 4.065-1.649.84-1.261.766-2.881.404-3.676.496-.135 1.08-.195 1.83-.104 2.101.24 2.521 1.56 2.43 2.1-.09.539-.523.854-.674.944-.15.091-.195.12-.181.181.015.09.091.09.21.075.165-.03 1.096-.45 1.141-1.471.045-1.29-1.186-2.729-3.375-2.7-.9.016-1.471.091-1.875.256-.03-.045-.061-.075-.105-.105-1.35-1.455-3.855-2.475-3.75-4.41.03-.705.285-2.564 4.8-4.814 3.705-1.846 6.661-1.335 7.171-.21.733 1.604-1.576 4.59-5.431 5.024-1.47.165-2.235-.404-2.431-.615-.209-.225-.239-.24-.314-.194-.12.06-.045.255 0 .375.12.3.585.825 1.396 1.095.704.225 2.43.359 4.5-.45 2.324-.899 4.139-3.405 3.614-5.505l.073.067z"/></svg>', - html: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', - htm: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', - xml: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', - json: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z"/></svg>', - md: '<svg fill="#083FA1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', - markdown: '<svg fill="#083FA1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', - txt: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', - dockerfile: '<svg fill="#2496ED" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>', - sh: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', - bash: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', - zsh: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', - bat: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', - cmd: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', - sql: '<svg fill="#4479A1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z"/></svg>', - mysql: '<svg fill="#4479A1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z"/></svg>', - sqlite: '<svg fill="#003B57" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.678.521c-1.032-.92-2.28-.55-3.513.544a8.71 8.71 0 0 0-.547.535c-2.109 2.237-4.066 6.38-4.674 9.544.237.48.422 1.093.544 1.561a13.044 13.044 0 0 1 .164.703s-.019-.071-.096-.296l-.05-.146a1.689 1.689 0 0 0-.033-.08c-.138-.32-.518-.995-.686-1.289-.143.423-.27.818-.376 1.176.484.884.778 2.4.778 2.4s-.025-.099-.147-.442c-.107-.303-.644-1.244-.772-1.464-.217.804-.304 1.346-.226 1.478.152.256.296.698.422 1.186.286 1.1.485 2.44.485 2.44l.017.224a22.41 22.41 0 0 0 .056 2.748c.095 1.146.273 2.13.5 2.657l.155-.084c-.334-1.038-.47-2.399-.41-3.967.09-2.398.642-5.29 1.661-8.304 1.723-4.55 4.113-8.201 6.3-9.945-1.993 1.8-4.692 7.63-5.5 9.788-.904 2.416-1.545 4.684-1.931 6.857.666-2.037 2.821-2.912 2.821-2.912s1.057-1.304 2.292-3.166c-.74.169-1.955.458-2.362.629-.6.251-.762.337-.762.337s1.945-1.184 3.613-1.72C21.695 7.9 24.195 2.767 21.678.521m-18.573.543A1.842 1.842 0 0 0 1.27 2.9v16.608a1.84 1.84 0 0 0 1.835 1.834h9.418a22.953 22.953 0 0 1-.052-2.707c-.006-.062-.011-.141-.016-.2a27.01 27.01 0 0 0-.473-2.378c-.121-.47-.275-.898-.369-1.057-.116-.197-.098-.31-.097-.432 0-.12.015-.245.037-.386a9.98 9.98 0 0 1 .234-1.045l.217-.028c-.017-.035-.014-.065-.031-.097l-.041-.381a32.8 32.8 0 0 1 .382-1.194l.2-.019c-.008-.016-.01-.038-.018-.053l-.043-.316c.63-3.28 2.587-7.443 4.8-9.791.066-.069.133-.128.198-.194Z"/></svg>', - postgresql: '<svg fill="#4169E1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z"/></svg>', - cpp: '<svg fill="#00599C" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z"/></svg>', - cxx: '<svg fill="#00599C" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z"/></svg>', - c: '<svg fill="#A8B9CC" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5921 9.1962s-.354-3.298-3.627-3.39c-3.2741-.09-4.9552 2.474-4.9552 6.14 0 3.6651 1.858 6.5972 5.0451 6.5972 3.184 0 3.5381-3.665 3.5381-3.665l6.1041.365s.36 3.31-2.196 5.836c-2.552 2.5241-5.6901 2.9371-7.8762 2.9201-2.19-.017-5.2261.034-8.1602-2.97-2.938-3.0101-3.436-5.9302-3.436-8.8002 0-2.8701.556-6.6702 4.047-9.5502C7.444.72 9.849 0 12.254 0c10.0422 0 10.7172 9.2602 10.7172 9.2602z"/></svg>', - h: '<svg fill="#A8B9CC" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5921 9.1962s-.354-3.298-3.627-3.39c-3.2741-.09-4.9552 2.474-4.9552 6.14 0 3.6651 1.858 6.5972 5.0451 6.5972 3.184 0 3.5381-3.665 3.5381-3.665l6.1041.365s.36 3.31-2.196 5.836c-2.552 2.5241-5.6901 2.9371-7.8762 2.9201-2.19-.017-5.2261.034-8.1602-2.97-2.938-3.0101-3.436-5.9302-3.436-8.8002 0-2.8701.556-6.6702 4.047-9.5502C7.444.72 9.849 0 12.254 0c10.0422 0 10.7172 9.2602 10.7172 9.2602z"/></svg>', - rb: '<svg fill="#CC342D" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20.156.083c3.033.525 3.893 2.598 3.829 4.77L24 4.822 22.635 22.71 4.89 23.926h.016C3.433 23.864.15 23.729 0 19.139l1.645-3 2.819 6.586.503 1.172 2.805-9.144-.03.007.016-.03 9.255 2.956-1.396-5.431-.99-3.9 8.82-.569-.615-.51L16.5 2.114 20.159.073l-.003.01zM0 19.089zM5.13 5.073c3.561-3.533 8.157-5.621 9.922-3.84 1.762 1.777-.105 6.105-3.673 9.636-3.563 3.532-8.103 5.734-9.864 3.957-1.766-1.777.045-6.217 3.612-9.75l.003-.003z"/></svg>', - php: '<svg fill="#777BB4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 0 1-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.265a2.782 2.782 0 0 1-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 0 1-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zM17.766 10.207h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>', - swift: '<svg fill="#F05138" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.508 0c-.287 0-.573 0-.86.002-.241.002-.483.003-.724.01-.132.003-.263.009-.395.015A9.154 9.154 0 0 0 4.348.15 5.492 5.492 0 0 0 2.85.645 5.04 5.04 0 0 0 .645 2.848c-.245.48-.4.972-.495 1.5-.093.52-.122 1.05-.136 1.576a35.2 35.2 0 0 0-.012.724C0 6.935 0 7.221 0 7.508v8.984c0 .287 0 .575.002.862.002.24.005.481.012.722.014.526.043 1.057.136 1.576.095.528.25 1.02.495 1.5a5.03 5.03 0 0 0 2.205 2.203c.48.244.97.4 1.498.495.52.093 1.05.124 1.576.138.241.007.483.009.724.01.287.002.573.002.86.002h8.984c.287 0 .573 0 .86-.002.241-.001.483-.003.724-.01a10.523 10.523 0 0 0 1.578-.138 5.322 5.322 0 0 0 1.498-.495 5.035 5.035 0 0 0 2.203-2.203c.245-.48.4-.972.495-1.5.093-.52.124-1.05.138-1.576.007-.241.009-.481.01-.722.002-.287.002-.575.002-.862V7.508c0-.287 0-.573-.002-.86a33.662 33.662 0 0 0-.01-.724 10.5 10.5 0 0 0-.138-1.576 5.328 5.328 0 0 0-.495-1.5A5.039 5.039 0 0 0 21.152.645 5.32 5.32 0 0 0 19.654.15a10.493 10.493 0 0 0-1.578-.138 34.98 34.98 0 0 0-.722-.01C17.067 0 16.779 0 16.492 0H7.508zm6.035 3.41c4.114 2.47 6.545 7.162 5.549 11.131-.024.093-.05.181-.076.272l.002.001c2.062 2.538 1.5 5.258 1.236 4.745-1.072-2.086-3.066-1.568-4.088-1.043a6.803 6.803 0 0 1-.281.158l-.02.012-.002.002c-2.115 1.123-4.957 1.205-7.812-.022a12.568 12.568 0 0 1-5.64-4.838c.649.48 1.35.902 2.097 1.252 3.019 1.414 6.051 1.311 8.197-.002C9.651 12.73 7.101 9.67 5.146 7.191a10.628 10.628 0 0 1-1.005-1.384c2.34 2.142 6.038 4.83 7.365 5.576C8.69 8.408 6.208 4.743 6.324 4.86c4.436 4.47 8.528 6.996 8.528 6.996.154.085.27.154.36.213.085-.215.16-.437.224-.668.708-2.588-.09-5.548-1.893-7.992z"/></svg>', - kt: '<svg fill="#7F52FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 24H0V0h24L12 12Z"/></svg>', - kotlin: '<svg fill="#7F52FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 24H0V0h24L12 12Z"/></svg>', - java: '<svg fill="#E76F00" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.915 0 11.7.215C9.515 2.4 7.47 6.39 6.046 10.483c-1.064 1.024-3.633 2.81-3.711 3.551-.093.87 1.746 2.611 1.55 3.235-.198.625-1.304 1.408-1.014 1.939.1.188.823.011 1.277-.491a13.389 13.389 0 0 0-.017 2.14c.076.906.27 1.668.643 2.232.372.563.956.911 1.667.911.397 0 .727-.114 1.024-.264.298-.149.571-.33.91-.5.68-.34 1.634-.666 3.53-.604 1.903.062 2.872.39 3.559.704.687.314 1.15.664 1.925.664.767 0 1.395-.336 1.807-.9.412-.563.631-1.33.72-2.24.06-.623.055-1.32 0-2.066.454.45 1.117.604 1.213.424.29-.53-.816-1.314-1.013-1.937-.198-.624 1.642-2.366 1.549-3.236-.08-.748-2.707-2.568-3.748-3.586C16.428 6.374 14.308 2.394 12.13.215zm.175 6.038a2.95 2.95 0 0 1 2.943 2.942 2.95 2.95 0 0 1-2.943 2.943A2.95 2.95 0 0 1 9.148 8.98a2.95 2.95 0 0 1 2.942-2.942zM8.685 7.983a3.515 3.515 0 0 0-.145.997c0 1.951 1.6 3.55 3.55 3.55 1.95 0 3.55-1.598 3.55-3.55 0-.329-.046-.648-.132-.951.334.095.64.208.915.336a42.699 42.699 0 0 1 2.042 5.829c.678 2.545 1.01 4.92.846 6.607-.082.844-.29 1.51-.606 1.94-.315.431-.713.651-1.315.651-.593 0-.932-.27-1.673-.61-.741-.338-1.825-.694-3.792-.758-1.974-.064-3.073.293-3.821.669-.375.188-.659.373-.911.5s-.466.2-.752.2c-.53 0-.876-.209-1.16-.64-.285-.43-.474-1.101-.545-1.948-.141-1.693.176-4.069.823-6.614a43.155 43.155 0 0 1 1.934-5.783c.348-.167.749-.31 1.192-.425zm-3.382 4.362a.216.216 0 0 1 .13.031c-.166.56-.323 1.116-.463 1.665a33.849 33.849 0 0 0-.547 2.555 3.9 3.9 0 0 0-.2-.39c-.58-1.012-.914-1.642-1.16-2.08.315-.24 1.679-1.755 2.24-1.781zm13.394.01c.562.027 1.926 1.543 2.24 1.783-.246.438-.58 1.068-1.16 2.08a4.428 4.428 0 0 0-.163.309 32.354 32.354 0 0 0-.562-2.49 40.579 40.579 0 0 0-.482-1.652.216.216 0 0 1 .127-.03z"/></svg>', - yaml: '<svg fill="#CB171E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z"/></svg>', - yml: '<svg fill="#CB171E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z"/></svg>', - toml: '<svg fill="#9C4121" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M.014 0h5.34v2.652H2.888v18.681h2.468V24H.015V0Zm17.622 5.049v2.78h-4.274v12.935h-3.008V7.83H6.059V5.05h11.577ZM23.986 24h-5.34v-2.652h2.467V2.667h-2.468V0h5.34v24Z"/></svg>', - gitignore: '<svg fill="#F03C2E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.09 23.549a1.54 1.54 0 0 1-2.18 0L.451 13.089a1.54 1.54 0 0 1 0-2.179l7.191-7.19 2.733 2.733a1.85 1.85 0 0 0 .964 2.326v6.66a1.849 1.849 0 1 0 1.54 0V8.957l2.508 2.508a1.85 1.85 0 1 0 1.09-1.09l-2.634-2.634a1.85 1.85 0 0 0-2.378-2.377L8.73 2.63 10.91.451a1.54 1.54 0 0 1 2.179 0l10.459 10.46a1.54 1.54 0 0 1 0 2.179z"/></svg>', - nodejs: '<svg fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', - node: '<svg fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', - mjs: '<svg fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', - cjs: '<svg fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', - redis: '<svg fill="#FF4438" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.71 13.145c-1.66 2.092-3.452 4.483-7.038 4.483-3.203 0-4.397-2.825-4.48-5.12.701 1.484 2.073 2.685 4.214 2.63 4.117-.133 6.94-3.852 6.94-7.239 0-4.05-3.022-6.972-8.268-6.972-3.752 0-8.4 1.428-11.455 3.685C2.59 6.937 3.885 9.958 4.35 9.626c2.648-1.904 4.748-3.13 6.784-3.744C8.12 9.244.886 17.05 0 18.425c.1 1.261 1.66 4.648 2.424 4.648.232 0 .431-.133.664-.365a100.49 100.49 0 0 0 5.54-6.765c.222 3.104 1.748 6.898 6.014 6.898 3.819 0 7.604-2.756 9.33-8.965.2-.764-.73-1.361-1.261-.73zm-4.349-5.013c0 1.959-1.926 2.922-3.685 2.922-.941 0-1.664-.247-2.235-.568 1.051-1.592 2.092-3.225 3.21-4.973 1.972.334 2.71 1.43 2.71 2.619z"/></svg>', - nginx: '<svg fill="#009639" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0L1.605 6v12L12 24l10.395-6V6L12 0zm6 16.59c0 .705-.646 1.29-1.529 1.29-.631 0-1.351-.255-1.801-.81l-6-7.141v6.66c0 .721-.57 1.29-1.274 1.29H7.32c-.721 0-1.29-.6-1.29-1.29V7.41c0-.705.63-1.29 1.5-1.29.646 0 1.38.255 1.83.81l5.97 7.141V7.41c0-.721.6-1.29 1.29-1.29h.075c.72 0 1.29.6 1.29 1.29v9.18H18z"/></svg>', - eslint: '<svg fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', - env: '<svg fill="#ECD53F" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 0v24H0V0h24ZM10.933 15.89H6.84v5.52h4.198v-.93H7.955v-1.503h2.77v-.93h-2.77v-1.224h2.978v-.934Zm2.146 0h-1.084v5.52h1.035v-3.6l2.226 3.6h1.118v-5.52h-1.036v3.686l-2.259-3.687Zm5.117 0h-1.208l1.973 5.52h1.19l1.976-5.52h-1.182l-1.352 4.085-1.397-4.086ZM5.4 19.68H3.72v1.68H5.4v-1.68Z"/></svg>', - editorconfig: '<svg fill="#FEFEFE" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10.325.402c-.242-.013-1.006.34-1.402.544-.897.472-1.14.632-1.806 1.54-.766 1.046-1.084 1.638-1.213 2.181-.383 1.392-.533 2.089-1.148 2.934-.457.344-.546.792-.911 1.305-.234.327-.862 1.43-.863 1.517-.003.163-.05.2-.539.184-.526-.018-.736.039-1.003.299-.24.232-.288.24-.289 1.054 0 .98.229 1.09.574 1.545C1.207 14.798.098 18.578 0 20.885c0 .195.015.706.131 1.741.137.296.337.514.587.671.822.062 1.762.302 3.39.302 1.315 0 3.857-.58 4.06-.635.693-.185 1.771-.76 2.51-1.187.174-.1 1.395-.542 1.856-.89.239-.181 1.461-.872 1.887-1.206.876-.687.96-.835 1.717-1.942.345-.505.497-.644.829-1.455l.01-.703c.262.09 2.184.608 2.41.743.367.219.012.448-.183.677-.14.165.092.323.224.203.192-.174.781-.564.744-.76 0-.134-.315-.328-.807-.572-.446-.221-.416-.218-.256-.265.905-.301 1.406-.437 2.032-.8 1.677-1.042 2.555-1.976 2.761-2.927.198-.876.09-1.52-.273-2.294-.183-.683-.513-.935-1.24-1.237-.848-.353-2.396-.51-3.012-.3-.517.175-1.777.738-2.416 1.057-.346.173-.633.307-.638.297-.156-.254-.32-.506-.463-.79-.252-.5-.37-.659-.624-.777-.133-.062-.385-.175-.684-.385-.496-.344-1.162-.82-2.723-1.243.892-1.545.904-1.74 1.039-2.587.125-.629.168-1.685.039-1.97-.117-.257-.617-.8-.955-.962-.66-.315-1.075-.287-1.627-.287zm.023.564c.172-.001.369.022.622.07.44.085.728.2 1.171.47l.3.182.065.525c.069.564.046.986-.073 1.348-.04.124-.134.424-.207.667-.145.484-.498 1.015-.636 1.236-.03.05-.125-.022-.166.08-.184.466-.4.487-.76.52-.113.01-.889-.046-1.687-.07-.963.028-1.337-.22-2.518.258-.253.096.528-1.41.61-1.56.21-.38 1.327-1.66 1.6-1.762.23-.086.456-.341.656-.532.33-.315.418-.348.765-.415.324-.063 1.066-.29 1.43-.037.225.178.381.537.346.821-.069.566-.149.792-.497 1.31-.824 1.227-.806 1.251-.699 1.313.105.06.085.094.067.161-.056.206.007.163.161.11.315-.11.802-.996 1.119-2.001.192-.611.19-.335.16-1.12-.017-.465-.029-.415-.182-.665-.227-.37-.45-.49-1.22-.49-.83-.002-1.143.033-1.676.515-.603.544-1.083.77-1.949 2.054-.427.461-.92 1.615-1.149 2.427-.029-.005-.043.02-.015-.366.043-.588.185-.988.79-2.225.429-.874.58-1.133.76-1.3.613-.57 1.66-1.217 2.34-1.445.153-.052.3-.078.472-.08zm1.285 6.017c1.406.541 2.34.62 3.442 1.654.68 1.108.9 2.22 1.246 3.435.343 1.06.33 2.738.321 2.97-.414-.157-.826-.197-1.292-.214-.06-.542-1.137-.636-1.531-.825-.512-.228-1.295-.364-2.3-.527-.96-.22-1.753-.111-2.722-.125-.11.1-.19.186-.278.317-.15-.08-.354-.275-.634-.264-.105 0-.117-.018-.08-.28.024-.168.102-.305.017-.383-.221-.204-.167-.218-.201-.316-1.107-.64-3.029-1.611-4.02-1.71.34-.476.195-.52.303-.691.272-.429.968-1.343 1.123-1.694 0-.229.723-.984 1.403-1.503 1.43-.28 3.565-.265 5.203.156zm8.519 1.505c1.605.041 2.166.236 2.697.777.524.55.698.877.698 1.31 0 .354-.278 1.343-.462 1.647-.7 1.152-2.265 2.404-3.6 2.88-.316.111-.478.133-1.096.148-.403.035-.91-.043-1.297-.13.22-2.561.026-2.73-.198-3.771.09-.22.505-.48.422-.611.776-.49 1.325-1.04 1.933-1.228.408-.15.493-.161 1.343-.186.802-.023.944-.014 1.224.075.597.19 1.265.746 1.265 1.053 0 .193.044.242.137.241.22-.002.151-.326.037-.559-.378-.704-.968-.954-1.533-1.081-.143-.03-.6-.099-1.013-.118-.591-.027-.84-.005-1.143.06-.48.102-.898.239-1.302.596-.31.275-.857.591-1.305.805-.106.015-.174-.17-.256-.267 1.053-.807 2.03-1.521 3.449-1.64zM2.718 10.96c-.304.488-.515.842-.524 1.226-.12.301-.227.595-.38.907-.373-.376-.254-1.176-.093-1.517.357-.69.368-.508.997-.616zm.798.111c1.097.336 2.247.64 3.698 1.535.278.167.13.5.024 1.034a1.675 1.675 0 0 1-1.133.607c.431-.434.878-1.195.402-1.714-.118-.156-.15-.181-.358-.166-.284.02-.288-.006-.44.389-.327.385-.492 1.292-.382 1.576-.566-.033-2.077-.152-2.95-.709.094-.243.335-.595.358-.914.119-.241.62-1.665.78-1.638zm2.7 1.709c.167.282-.192 1.135-.417 1.241-.165-.602.167-1.17.416-1.241zm3.534.831c.437 0 1.24.092 2.21.253.836.138.995.177 1.715.414.854.264.798.364 1.087.785.063.09.156.366.205.609.109.533-.402.943-1.44 1.157-.354.073-.673.097-1.23.093-.879-.007-1.19-.044-1.685-.2-.736-.231-1.114-.555-1.519-1.233-.22-.37-.273-.414-.242-.685.064-.562.183-.927.346-1.064.134-.113.203-.129.553-.129zm-1.984.085c.1-.003.586.224.683.29-.142.385-.009 1.322.383 1.777.092.143.318.465.535.68.684.683 1.13.795 2.656.796 1.278.002 1.65-.068 2.285-.272.589-.188.768-.428.837-.595.17-.402.252-.817.211-1.247l1.287.366c-.106.866-.587 1.375-.944 1.821-.266.428-.816 1.23-.971 1.364-.747.643-1.436 1.095-2.928 1.936-.439.248-1.618.706-1.924.859-.312.156-1.417.81-1.569.855a9.49 9.49 0 0 1-1.554.315c-.4.047-1.82.262-2.725.299-.66.014-.84-.054-1.657-.03-.438-.084-1.396-.301-1.396-.301-.285-.457-.359-.807-.439-1.336.664.778 1.063 1.041 1.595 1.041.479 0 .725-.18.92-.18.123 0 .454-.233.437-.385a1.03 1.03 0 0 0-.253-.605c-.23-.147-1.6-.691-1.832-.691-.64 0-.781.05-.85.18.395-2.142.909-4.616 1.67-6.62 1.174.514 3.097.887 4.043.568 1.452-.51 1.12-.685 1.5-.885zm3.817.15c-.168 0-.586.458-.744.815-.159.359-.202.911-.083 1.055.098.118.503.12.707-.004.398-.243.779-1.096.682-1.538-.035-.16-.33-.328-.562-.328zm.026.39c.148.178-.04.659-.213.85-.204.216-.406.247-.348.058.14-.353.456-.914.561-.908zM2.68 21.462c.156.049.353.27.315.33-.638.036-.998-.008-1.051-.012-.217-.017-.917-.32-.897-.657.027-.395 1.32.31 1.633.339z"/></svg>', - webpack: '<svg fill="#8DD6F9" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.1987 18.498l-9.7699 5.5022v-4.2855l6.0872-3.3338 3.6826 2.117zm.6683-.6026V6.3884l-3.5752 2.0544v7.396zm-21.0657.6026l9.7699 5.5022v-4.2855L5.484 16.3809l-3.6826 2.117zm-.6683-.6026V6.3884l3.5751 2.0544v7.396zm.4183-12.2515l10.0199-5.644v4.1434L5.152 7.6586l-.0489.028zm20.8975 0l-10.02-5.644v4.1434l6.4192 3.5154.0489.028 3.5518-2.0427zm-10.8775 13.096l-6.0056-3.2873V8.9384l6.0054 3.4525v6.349zm.8575 0l6.0053-3.2873V8.9384l-6.0053 3.4525zM5.9724 8.1845l6.0287-3.3015L18.03 8.1845l-6.0288 3.4665z"/></svg>', - vite: '<svg fill="#9135FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.056 23.238a.57.57 0 0 1-1.02-.355v-5.202c0-.63-.512-1.143-1.144-1.143H5.148a.57.57 0 0 1-.464-.903l3.777-5.29c.54-.753 0-1.804-.93-1.804H.57a.574.574 0 0 1-.543-.746.6.6 0 0 1 .08-.157L5.008.78a.57.57 0 0 1 .467-.24h14.589a.57.57 0 0 1 .466.903l-3.778 5.29c-.54.755 0 1.806.93 1.806h5.745c.238 0 .424.138.513.322a.56.56 0 0 1-.063.603z"/></svg>', - npmrc: '<svg fill="#CB3837" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>', - pnpm: '<svg fill="#F69220" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM2 2h3.5v3.5H2zm8.25 0h3.498v3.5H10.25zm8.25 0H22v3.5h-3.5zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zm2 2H22v3.5h-3.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z"/></svg>', - yarn: '<svg fill="#2C8EBB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z"/></svg>', - npmignore: '<svg fill="#CB3837" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>', - eslintrc: '<svg fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', - nycrc: '<svg fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', - jshintrc: '<svg fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', - gitkeep: '<svg fill="#F03C2E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.09 23.549a1.54 1.54 0 0 1-2.18 0L.451 13.089a1.54 1.54 0 0 1 0-2.179l7.191-7.19 2.733 2.733a1.85 1.85 0 0 0 .964 2.326v6.66a1.849 1.849 0 1 0 1.54 0V8.957l2.508 2.508a1.85 1.85 0 1 0 1.09-1.09l-2.634-2.634a1.85 1.85 0 0 0-2.378-2.377L8.73 2.63 10.91.451a1.54 1.54 0 0 1 2.179 0l10.459 10.46a1.54 1.54 0 0 1 0 2.179z"/></svg>', - ps1: '<svg fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', // PowerShell → 终端图标 - wxss: '<svg fill="#663399" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63"/></svg>', // 小程序样式 → CSS 图标 - wxml: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', // 小程序标记 → HTML 图标 - jsonl: '<svg fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z"/></svg>', // JSON Lines → JSON 图标 - dockerignore: '<svg fill="#2496ED" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>', // Docker 忽略 → Docker 图标 - png: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', svg: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', jpg: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', jpeg: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', gif: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', webp: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', bmp: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', ico: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', icns: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', // 图片类 - lock: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-1V7a5 5 0 0 0-5-5zm-3 8V7a3 3 0 0 1 6 0v3H9z"/></svg>', // Cargo.lock 等 - conf: '<svg fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm7 1.5V7h3.5L13 3.5zM8 12h8v1.5H8V12zm0 3.5h8V17H8v-1.5z"/></svg>', // 配置文件 + go: '<svg aria-hidden="true" fill="#00ADD8" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z"/></svg>', + python: '<svg aria-hidden="true" fill="#3776AB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>', + py: '<svg aria-hidden="true" fill="#3776AB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>', + rs: '<svg aria-hidden="true" fill="#DE5842" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z"/></svg>', + rust: '<svg aria-hidden="true" fill="#DE5842" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z"/></svg>', + ts: '<svg aria-hidden="true" fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', + tsx: '<svg aria-hidden="true" fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', + mts: '<svg aria-hidden="true" fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', + cts: '<svg aria-hidden="true" fill="#3178C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>', + js: '<svg aria-hidden="true" fill="#F7DF1E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>', + jsx: '<svg aria-hidden="true" fill="#F7DF1E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z"/></svg>', + vue: '<svg aria-hidden="true" fill="#4FC08D" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z"/></svg>', + css: '<svg aria-hidden="true" fill="#663399" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63"/></svg>', + scss: '<svg aria-hidden="true" fill="#CC6699" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0zM9.615 15.998c.175.645.156 1.248-.024 1.792l-.065.18c-.024.061-.052.12-.078.176-.14.29-.326.56-.555.81-.698.759-1.672 1.047-2.09.805-.45-.262-.226-1.335.584-2.19.871-.918 2.12-1.509 2.12-1.509v-.003l.108-.061zm9.911-10.861c-.542-2.133-4.077-2.834-7.422-1.645-1.989.707-4.144 1.818-5.693 3.267C4.568 8.48 4.275 9.98 4.396 10.607c.427 2.211 3.457 3.657 4.703 4.73v.006c-.367.18-3.056 1.529-3.686 2.925-.675 1.47.105 2.521.615 2.655 1.575.436 3.195-.36 4.065-1.649.84-1.261.766-2.881.404-3.676.496-.135 1.08-.195 1.83-.104 2.101.24 2.521 1.56 2.43 2.1-.09.539-.523.854-.674.944-.15.091-.195.12-.181.181.015.09.091.09.21.075.165-.03 1.096-.45 1.141-1.471.045-1.29-1.186-2.729-3.375-2.7-.9.016-1.471.091-1.875.256-.03-.045-.061-.075-.105-.105-1.35-1.455-3.855-2.475-3.75-4.41.03-.705.285-2.564 4.8-4.814 3.705-1.846 6.661-1.335 7.171-.21.733 1.604-1.576 4.59-5.431 5.024-1.47.165-2.235-.404-2.431-.615-.209-.225-.239-.24-.314-.194-.12.06-.045.255 0 .375.12.3.585.825 1.396 1.095.704.225 2.43.359 4.5-.45 2.324-.899 4.139-3.405 3.614-5.505l.073.067z"/></svg>', + html: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', + htm: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', + xml: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', + json: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z"/></svg>', + md: '<svg aria-hidden="true" fill="#083FA1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', + markdown: '<svg aria-hidden="true" fill="#083FA1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', + txt: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>', + dockerfile: '<svg aria-hidden="true" fill="#2496ED" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>', + sh: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', + bash: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', + zsh: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', + bat: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', + cmd: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', + sql: '<svg aria-hidden="true" fill="#4479A1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z"/></svg>', + mysql: '<svg aria-hidden="true" fill="#4479A1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z"/></svg>', + sqlite: '<svg aria-hidden="true" fill="#003B57" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.678.521c-1.032-.92-2.28-.55-3.513.544a8.71 8.71 0 0 0-.547.535c-2.109 2.237-4.066 6.38-4.674 9.544.237.48.422 1.093.544 1.561a13.044 13.044 0 0 1 .164.703s-.019-.071-.096-.296l-.05-.146a1.689 1.689 0 0 0-.033-.08c-.138-.32-.518-.995-.686-1.289-.143.423-.27.818-.376 1.176.484.884.778 2.4.778 2.4s-.025-.099-.147-.442c-.107-.303-.644-1.244-.772-1.464-.217.804-.304 1.346-.226 1.478.152.256.296.698.422 1.186.286 1.1.485 2.44.485 2.44l.017.224a22.41 22.41 0 0 0 .056 2.748c.095 1.146.273 2.13.5 2.657l.155-.084c-.334-1.038-.47-2.399-.41-3.967.09-2.398.642-5.29 1.661-8.304 1.723-4.55 4.113-8.201 6.3-9.945-1.993 1.8-4.692 7.63-5.5 9.788-.904 2.416-1.545 4.684-1.931 6.857.666-2.037 2.821-2.912 2.821-2.912s1.057-1.304 2.292-3.166c-.74.169-1.955.458-2.362.629-.6.251-.762.337-.762.337s1.945-1.184 3.613-1.72C21.695 7.9 24.195 2.767 21.678.521m-18.573.543A1.842 1.842 0 0 0 1.27 2.9v16.608a1.84 1.84 0 0 0 1.835 1.834h9.418a22.953 22.953 0 0 1-.052-2.707c-.006-.062-.011-.141-.016-.2a27.01 27.01 0 0 0-.473-2.378c-.121-.47-.275-.898-.369-1.057-.116-.197-.098-.31-.097-.432 0-.12.015-.245.037-.386a9.98 9.98 0 0 1 .234-1.045l.217-.028c-.017-.035-.014-.065-.031-.097l-.041-.381a32.8 32.8 0 0 1 .382-1.194l.2-.019c-.008-.016-.01-.038-.018-.053l-.043-.316c.63-3.28 2.587-7.443 4.8-9.791.066-.069.133-.128.198-.194Z"/></svg>', + postgresql: '<svg aria-hidden="true" fill="#4169E1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z"/></svg>', + cpp: '<svg aria-hidden="true" fill="#00599C" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z"/></svg>', + cxx: '<svg aria-hidden="true" fill="#00599C" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z"/></svg>', + c: '<svg aria-hidden="true" fill="#A8B9CC" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5921 9.1962s-.354-3.298-3.627-3.39c-3.2741-.09-4.9552 2.474-4.9552 6.14 0 3.6651 1.858 6.5972 5.0451 6.5972 3.184 0 3.5381-3.665 3.5381-3.665l6.1041.365s.36 3.31-2.196 5.836c-2.552 2.5241-5.6901 2.9371-7.8762 2.9201-2.19-.017-5.2261.034-8.1602-2.97-2.938-3.0101-3.436-5.9302-3.436-8.8002 0-2.8701.556-6.6702 4.047-9.5502C7.444.72 9.849 0 12.254 0c10.0422 0 10.7172 9.2602 10.7172 9.2602z"/></svg>', + h: '<svg aria-hidden="true" fill="#A8B9CC" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16.5921 9.1962s-.354-3.298-3.627-3.39c-3.2741-.09-4.9552 2.474-4.9552 6.14 0 3.6651 1.858 6.5972 5.0451 6.5972 3.184 0 3.5381-3.665 3.5381-3.665l6.1041.365s.36 3.31-2.196 5.836c-2.552 2.5241-5.6901 2.9371-7.8762 2.9201-2.19-.017-5.2261.034-8.1602-2.97-2.938-3.0101-3.436-5.9302-3.436-8.8002 0-2.8701.556-6.6702 4.047-9.5502C7.444.72 9.849 0 12.254 0c10.0422 0 10.7172 9.2602 10.7172 9.2602z"/></svg>', + rb: '<svg aria-hidden="true" fill="#CC342D" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20.156.083c3.033.525 3.893 2.598 3.829 4.77L24 4.822 22.635 22.71 4.89 23.926h.016C3.433 23.864.15 23.729 0 19.139l1.645-3 2.819 6.586.503 1.172 2.805-9.144-.03.007.016-.03 9.255 2.956-1.396-5.431-.99-3.9 8.82-.569-.615-.51L16.5 2.114 20.159.073l-.003.01zM0 19.089zM5.13 5.073c3.561-3.533 8.157-5.621 9.922-3.84 1.762 1.777-.105 6.105-3.673 9.636-3.563 3.532-8.103 5.734-9.864 3.957-1.766-1.777.045-6.217 3.612-9.75l.003-.003z"/></svg>', + php: '<svg aria-hidden="true" fill="#777BB4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 0 1-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.265a2.782 2.782 0 0 1-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 0 1-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zM17.766 10.207h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>', + swift: '<svg aria-hidden="true" fill="#F05138" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.508 0c-.287 0-.573 0-.86.002-.241.002-.483.003-.724.01-.132.003-.263.009-.395.015A9.154 9.154 0 0 0 4.348.15 5.492 5.492 0 0 0 2.85.645 5.04 5.04 0 0 0 .645 2.848c-.245.48-.4.972-.495 1.5-.093.52-.122 1.05-.136 1.576a35.2 35.2 0 0 0-.012.724C0 6.935 0 7.221 0 7.508v8.984c0 .287 0 .575.002.862.002.24.005.481.012.722.014.526.043 1.057.136 1.576.095.528.25 1.02.495 1.5a5.03 5.03 0 0 0 2.205 2.203c.48.244.97.4 1.498.495.52.093 1.05.124 1.576.138.241.007.483.009.724.01.287.002.573.002.86.002h8.984c.287 0 .573 0 .86-.002.241-.001.483-.003.724-.01a10.523 10.523 0 0 0 1.578-.138 5.322 5.322 0 0 0 1.498-.495 5.035 5.035 0 0 0 2.203-2.203c.245-.48.4-.972.495-1.5.093-.52.124-1.05.138-1.576.007-.241.009-.481.01-.722.002-.287.002-.575.002-.862V7.508c0-.287 0-.573-.002-.86a33.662 33.662 0 0 0-.01-.724 10.5 10.5 0 0 0-.138-1.576 5.328 5.328 0 0 0-.495-1.5A5.039 5.039 0 0 0 21.152.645 5.32 5.32 0 0 0 19.654.15a10.493 10.493 0 0 0-1.578-.138 34.98 34.98 0 0 0-.722-.01C17.067 0 16.779 0 16.492 0H7.508zm6.035 3.41c4.114 2.47 6.545 7.162 5.549 11.131-.024.093-.05.181-.076.272l.002.001c2.062 2.538 1.5 5.258 1.236 4.745-1.072-2.086-3.066-1.568-4.088-1.043a6.803 6.803 0 0 1-.281.158l-.02.012-.002.002c-2.115 1.123-4.957 1.205-7.812-.022a12.568 12.568 0 0 1-5.64-4.838c.649.48 1.35.902 2.097 1.252 3.019 1.414 6.051 1.311 8.197-.002C9.651 12.73 7.101 9.67 5.146 7.191a10.628 10.628 0 0 1-1.005-1.384c2.34 2.142 6.038 4.83 7.365 5.576C8.69 8.408 6.208 4.743 6.324 4.86c4.436 4.47 8.528 6.996 8.528 6.996.154.085.27.154.36.213.085-.215.16-.437.224-.668.708-2.588-.09-5.548-1.893-7.992z"/></svg>', + kt: '<svg aria-hidden="true" fill="#7F52FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 24H0V0h24L12 12Z"/></svg>', + kotlin: '<svg aria-hidden="true" fill="#7F52FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 24H0V0h24L12 12Z"/></svg>', + java: '<svg aria-hidden="true" fill="#E76F00" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.915 0 11.7.215C9.515 2.4 7.47 6.39 6.046 10.483c-1.064 1.024-3.633 2.81-3.711 3.551-.093.87 1.746 2.611 1.55 3.235-.198.625-1.304 1.408-1.014 1.939.1.188.823.011 1.277-.491a13.389 13.389 0 0 0-.017 2.14c.076.906.27 1.668.643 2.232.372.563.956.911 1.667.911.397 0 .727-.114 1.024-.264.298-.149.571-.33.91-.5.68-.34 1.634-.666 3.53-.604 1.903.062 2.872.39 3.559.704.687.314 1.15.664 1.925.664.767 0 1.395-.336 1.807-.9.412-.563.631-1.33.72-2.24.06-.623.055-1.32 0-2.066.454.45 1.117.604 1.213.424.29-.53-.816-1.314-1.013-1.937-.198-.624 1.642-2.366 1.549-3.236-.08-.748-2.707-2.568-3.748-3.586C16.428 6.374 14.308 2.394 12.13.215zm.175 6.038a2.95 2.95 0 0 1 2.943 2.942 2.95 2.95 0 0 1-2.943 2.943A2.95 2.95 0 0 1 9.148 8.98a2.95 2.95 0 0 1 2.942-2.942zM8.685 7.983a3.515 3.515 0 0 0-.145.997c0 1.951 1.6 3.55 3.55 3.55 1.95 0 3.55-1.598 3.55-3.55 0-.329-.046-.648-.132-.951.334.095.64.208.915.336a42.699 42.699 0 0 1 2.042 5.829c.678 2.545 1.01 4.92.846 6.607-.082.844-.29 1.51-.606 1.94-.315.431-.713.651-1.315.651-.593 0-.932-.27-1.673-.61-.741-.338-1.825-.694-3.792-.758-1.974-.064-3.073.293-3.821.669-.375.188-.659.373-.911.5s-.466.2-.752.2c-.53 0-.876-.209-1.16-.64-.285-.43-.474-1.101-.545-1.948-.141-1.693.176-4.069.823-6.614a43.155 43.155 0 0 1 1.934-5.783c.348-.167.749-.31 1.192-.425zm-3.382 4.362a.216.216 0 0 1 .13.031c-.166.56-.323 1.116-.463 1.665a33.849 33.849 0 0 0-.547 2.555 3.9 3.9 0 0 0-.2-.39c-.58-1.012-.914-1.642-1.16-2.08.315-.24 1.679-1.755 2.24-1.781zm13.394.01c.562.027 1.926 1.543 2.24 1.783-.246.438-.58 1.068-1.16 2.08a4.428 4.428 0 0 0-.163.309 32.354 32.354 0 0 0-.562-2.49 40.579 40.579 0 0 0-.482-1.652.216.216 0 0 1 .127-.03z"/></svg>', + yaml: '<svg aria-hidden="true" fill="#CB171E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z"/></svg>', + yml: '<svg aria-hidden="true" fill="#CB171E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z"/></svg>', + toml: '<svg aria-hidden="true" fill="#9C4121" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M.014 0h5.34v2.652H2.888v18.681h2.468V24H.015V0Zm17.622 5.049v2.78h-4.274v12.935h-3.008V7.83H6.059V5.05h11.577ZM23.986 24h-5.34v-2.652h2.467V2.667h-2.468V0h5.34v24Z"/></svg>', + gitignore: '<svg aria-hidden="true" fill="#F03C2E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.09 23.549a1.54 1.54 0 0 1-2.18 0L.451 13.089a1.54 1.54 0 0 1 0-2.179l7.191-7.19 2.733 2.733a1.85 1.85 0 0 0 .964 2.326v6.66a1.849 1.849 0 1 0 1.54 0V8.957l2.508 2.508a1.85 1.85 0 1 0 1.09-1.09l-2.634-2.634a1.85 1.85 0 0 0-2.378-2.377L8.73 2.63 10.91.451a1.54 1.54 0 0 1 2.179 0l10.459 10.46a1.54 1.54 0 0 1 0 2.179z"/></svg>', + nodejs: '<svg aria-hidden="true" fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', + node: '<svg aria-hidden="true" fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', + mjs: '<svg aria-hidden="true" fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', + cjs: '<svg aria-hidden="true" fill="#5FA04E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"/></svg>', + redis: '<svg aria-hidden="true" fill="#FF4438" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.71 13.145c-1.66 2.092-3.452 4.483-7.038 4.483-3.203 0-4.397-2.825-4.48-5.12.701 1.484 2.073 2.685 4.214 2.63 4.117-.133 6.94-3.852 6.94-7.239 0-4.05-3.022-6.972-8.268-6.972-3.752 0-8.4 1.428-11.455 3.685C2.59 6.937 3.885 9.958 4.35 9.626c2.648-1.904 4.748-3.13 6.784-3.744C8.12 9.244.886 17.05 0 18.425c.1 1.261 1.66 4.648 2.424 4.648.232 0 .431-.133.664-.365a100.49 100.49 0 0 0 5.54-6.765c.222 3.104 1.748 6.898 6.014 6.898 3.819 0 7.604-2.756 9.33-8.965.2-.764-.73-1.361-1.261-.73zm-4.349-5.013c0 1.959-1.926 2.922-3.685 2.922-.941 0-1.664-.247-2.235-.568 1.051-1.592 2.092-3.225 3.21-4.973 1.972.334 2.71 1.43 2.71 2.619z"/></svg>', + nginx: '<svg aria-hidden="true" fill="#009639" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0L1.605 6v12L12 24l10.395-6V6L12 0zm6 16.59c0 .705-.646 1.29-1.529 1.29-.631 0-1.351-.255-1.801-.81l-6-7.141v6.66c0 .721-.57 1.29-1.274 1.29H7.32c-.721 0-1.29-.6-1.29-1.29V7.41c0-.705.63-1.29 1.5-1.29.646 0 1.38.255 1.83.81l5.97 7.141V7.41c0-.721.6-1.29 1.29-1.29h.075c.72 0 1.29.6 1.29 1.29v9.18H18z"/></svg>', + eslint: '<svg aria-hidden="true" fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', + env: '<svg aria-hidden="true" fill="#ECD53F" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 0v24H0V0h24ZM10.933 15.89H6.84v5.52h4.198v-.93H7.955v-1.503h2.77v-.93h-2.77v-1.224h2.978v-.934Zm2.146 0h-1.084v5.52h1.035v-3.6l2.226 3.6h1.118v-5.52h-1.036v3.686l-2.259-3.687Zm5.117 0h-1.208l1.973 5.52h1.19l1.976-5.52h-1.182l-1.352 4.085-1.397-4.086ZM5.4 19.68H3.72v1.68H5.4v-1.68Z"/></svg>', + editorconfig: '<svg aria-hidden="true" fill="#FEFEFE" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10.325.402c-.242-.013-1.006.34-1.402.544-.897.472-1.14.632-1.806 1.54-.766 1.046-1.084 1.638-1.213 2.181-.383 1.392-.533 2.089-1.148 2.934-.457.344-.546.792-.911 1.305-.234.327-.862 1.43-.863 1.517-.003.163-.05.2-.539.184-.526-.018-.736.039-1.003.299-.24.232-.288.24-.289 1.054 0 .98.229 1.09.574 1.545C1.207 14.798.098 18.578 0 20.885c0 .195.015.706.131 1.741.137.296.337.514.587.671.822.062 1.762.302 3.39.302 1.315 0 3.857-.58 4.06-.635.693-.185 1.771-.76 2.51-1.187.174-.1 1.395-.542 1.856-.89.239-.181 1.461-.872 1.887-1.206.876-.687.96-.835 1.717-1.942.345-.505.497-.644.829-1.455l.01-.703c.262.09 2.184.608 2.41.743.367.219.012.448-.183.677-.14.165.092.323.224.203.192-.174.781-.564.744-.76 0-.134-.315-.328-.807-.572-.446-.221-.416-.218-.256-.265.905-.301 1.406-.437 2.032-.8 1.677-1.042 2.555-1.976 2.761-2.927.198-.876.09-1.52-.273-2.294-.183-.683-.513-.935-1.24-1.237-.848-.353-2.396-.51-3.012-.3-.517.175-1.777.738-2.416 1.057-.346.173-.633.307-.638.297-.156-.254-.32-.506-.463-.79-.252-.5-.37-.659-.624-.777-.133-.062-.385-.175-.684-.385-.496-.344-1.162-.82-2.723-1.243.892-1.545.904-1.74 1.039-2.587.125-.629.168-1.685.039-1.97-.117-.257-.617-.8-.955-.962-.66-.315-1.075-.287-1.627-.287zm.023.564c.172-.001.369.022.622.07.44.085.728.2 1.171.47l.3.182.065.525c.069.564.046.986-.073 1.348-.04.124-.134.424-.207.667-.145.484-.498 1.015-.636 1.236-.03.05-.125-.022-.166.08-.184.466-.4.487-.76.52-.113.01-.889-.046-1.687-.07-.963.028-1.337-.22-2.518.258-.253.096.528-1.41.61-1.56.21-.38 1.327-1.66 1.6-1.762.23-.086.456-.341.656-.532.33-.315.418-.348.765-.415.324-.063 1.066-.29 1.43-.037.225.178.381.537.346.821-.069.566-.149.792-.497 1.31-.824 1.227-.806 1.251-.699 1.313.105.06.085.094.067.161-.056.206.007.163.161.11.315-.11.802-.996 1.119-2.001.192-.611.19-.335.16-1.12-.017-.465-.029-.415-.182-.665-.227-.37-.45-.49-1.22-.49-.83-.002-1.143.033-1.676.515-.603.544-1.083.77-1.949 2.054-.427.461-.92 1.615-1.149 2.427-.029-.005-.043.02-.015-.366.043-.588.185-.988.79-2.225.429-.874.58-1.133.76-1.3.613-.57 1.66-1.217 2.34-1.445.153-.052.3-.078.472-.08zm1.285 6.017c1.406.541 2.34.62 3.442 1.654.68 1.108.9 2.22 1.246 3.435.343 1.06.33 2.738.321 2.97-.414-.157-.826-.197-1.292-.214-.06-.542-1.137-.636-1.531-.825-.512-.228-1.295-.364-2.3-.527-.96-.22-1.753-.111-2.722-.125-.11.1-.19.186-.278.317-.15-.08-.354-.275-.634-.264-.105 0-.117-.018-.08-.28.024-.168.102-.305.017-.383-.221-.204-.167-.218-.201-.316-1.107-.64-3.029-1.611-4.02-1.71.34-.476.195-.52.303-.691.272-.429.968-1.343 1.123-1.694 0-.229.723-.984 1.403-1.503 1.43-.28 3.565-.265 5.203.156zm8.519 1.505c1.605.041 2.166.236 2.697.777.524.55.698.877.698 1.31 0 .354-.278 1.343-.462 1.647-.7 1.152-2.265 2.404-3.6 2.88-.316.111-.478.133-1.096.148-.403.035-.91-.043-1.297-.13.22-2.561.026-2.73-.198-3.771.09-.22.505-.48.422-.611.776-.49 1.325-1.04 1.933-1.228.408-.15.493-.161 1.343-.186.802-.023.944-.014 1.224.075.597.19 1.265.746 1.265 1.053 0 .193.044.242.137.241.22-.002.151-.326.037-.559-.378-.704-.968-.954-1.533-1.081-.143-.03-.6-.099-1.013-.118-.591-.027-.84-.005-1.143.06-.48.102-.898.239-1.302.596-.31.275-.857.591-1.305.805-.106.015-.174-.17-.256-.267 1.053-.807 2.03-1.521 3.449-1.64zM2.718 10.96c-.304.488-.515.842-.524 1.226-.12.301-.227.595-.38.907-.373-.376-.254-1.176-.093-1.517.357-.69.368-.508.997-.616zm.798.111c1.097.336 2.247.64 3.698 1.535.278.167.13.5.024 1.034a1.675 1.675 0 0 1-1.133.607c.431-.434.878-1.195.402-1.714-.118-.156-.15-.181-.358-.166-.284.02-.288-.006-.44.389-.327.385-.492 1.292-.382 1.576-.566-.033-2.077-.152-2.95-.709.094-.243.335-.595.358-.914.119-.241.62-1.665.78-1.638zm2.7 1.709c.167.282-.192 1.135-.417 1.241-.165-.602.167-1.17.416-1.241zm3.534.831c.437 0 1.24.092 2.21.253.836.138.995.177 1.715.414.854.264.798.364 1.087.785.063.09.156.366.205.609.109.533-.402.943-1.44 1.157-.354.073-.673.097-1.23.093-.879-.007-1.19-.044-1.685-.2-.736-.231-1.114-.555-1.519-1.233-.22-.37-.273-.414-.242-.685.064-.562.183-.927.346-1.064.134-.113.203-.129.553-.129zm-1.984.085c.1-.003.586.224.683.29-.142.385-.009 1.322.383 1.777.092.143.318.465.535.68.684.683 1.13.795 2.656.796 1.278.002 1.65-.068 2.285-.272.589-.188.768-.428.837-.595.17-.402.252-.817.211-1.247l1.287.366c-.106.866-.587 1.375-.944 1.821-.266.428-.816 1.23-.971 1.364-.747.643-1.436 1.095-2.928 1.936-.439.248-1.618.706-1.924.859-.312.156-1.417.81-1.569.855a9.49 9.49 0 0 1-1.554.315c-.4.047-1.82.262-2.725.299-.66.014-.84-.054-1.657-.03-.438-.084-1.396-.301-1.396-.301-.285-.457-.359-.807-.439-1.336.664.778 1.063 1.041 1.595 1.041.479 0 .725-.18.92-.18.123 0 .454-.233.437-.385a1.03 1.03 0 0 0-.253-.605c-.23-.147-1.6-.691-1.832-.691-.64 0-.781.05-.85.18.395-2.142.909-4.616 1.67-6.62 1.174.514 3.097.887 4.043.568 1.452-.51 1.12-.685 1.5-.885zm3.817.15c-.168 0-.586.458-.744.815-.159.359-.202.911-.083 1.055.098.118.503.12.707-.004.398-.243.779-1.096.682-1.538-.035-.16-.33-.328-.562-.328zm.026.39c.148.178-.04.659-.213.85-.204.216-.406.247-.348.058.14-.353.456-.914.561-.908zM2.68 21.462c.156.049.353.27.315.33-.638.036-.998-.008-1.051-.012-.217-.017-.917-.32-.897-.657.027-.395 1.32.31 1.633.339z"/></svg>', + webpack: '<svg aria-hidden="true" fill="#8DD6F9" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.1987 18.498l-9.7699 5.5022v-4.2855l6.0872-3.3338 3.6826 2.117zm.6683-.6026V6.3884l-3.5752 2.0544v7.396zm-21.0657.6026l9.7699 5.5022v-4.2855L5.484 16.3809l-3.6826 2.117zm-.6683-.6026V6.3884l3.5751 2.0544v7.396zm.4183-12.2515l10.0199-5.644v4.1434L5.152 7.6586l-.0489.028zm20.8975 0l-10.02-5.644v4.1434l6.4192 3.5154.0489.028 3.5518-2.0427zm-10.8775 13.096l-6.0056-3.2873V8.9384l6.0054 3.4525v6.349zm.8575 0l6.0053-3.2873V8.9384l-6.0053 3.4525zM5.9724 8.1845l6.0287-3.3015L18.03 8.1845l-6.0288 3.4665z"/></svg>', + vite: '<svg aria-hidden="true" fill="#9135FF" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.056 23.238a.57.57 0 0 1-1.02-.355v-5.202c0-.63-.512-1.143-1.144-1.143H5.148a.57.57 0 0 1-.464-.903l3.777-5.29c.54-.753 0-1.804-.93-1.804H.57a.574.574 0 0 1-.543-.746.6.6 0 0 1 .08-.157L5.008.78a.57.57 0 0 1 .467-.24h14.589a.57.57 0 0 1 .466.903l-3.778 5.29c-.54.755 0 1.806.93 1.806h5.745c.238 0 .424.138.513.322a.56.56 0 0 1-.063.603z"/></svg>', + npmrc: '<svg aria-hidden="true" fill="#CB3837" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>', + pnpm: '<svg aria-hidden="true" fill="#F69220" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM2 2h3.5v3.5H2zm8.25 0h3.498v3.5H10.25zm8.25 0H22v3.5h-3.5zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zm2 2H22v3.5h-3.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z"/></svg>', + yarn: '<svg aria-hidden="true" fill="#2C8EBB" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z"/></svg>', + npmignore: '<svg aria-hidden="true" fill="#CB3837" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>', + eslintrc: '<svg aria-hidden="true" fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', + nycrc: '<svg aria-hidden="true" fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', + jshintrc: '<svg aria-hidden="true" fill="#4B32C3" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.257 9.132L11.816 6.5a.369.369 0 0 1 .368 0l4.559 2.632a.369.369 0 0 1 .184.32v5.263a.37.37 0 0 1-.184.319l-4.559 2.632a.369.369 0 0 1-.368 0l-4.559-2.632a.369.369 0 0 1-.184-.32V9.452a.37.37 0 0 1 .184-.32M23.852 11.53l-5.446-9.475c-.198-.343-.564-.596-.96-.596H6.555c-.396 0-.762.253-.96.596L.149 11.509a1.127 1.127 0 0 0 0 1.117l5.447 9.398c.197.342.563.517.959.517h10.893c.395 0 .76-.17.959-.512l5.446-9.413a1.069 1.069 0 0 0 0-1.086m-4.51 4.556a.4.4 0 0 1-.204.338L12.2 20.426a.395.395 0 0 1-.392 0l-6.943-4.002a.4.4 0 0 1-.205-.338V8.08c0-.14.083-.269.204-.338L11.8 3.74c.12-.07.272-.07.392 0l6.943 4.003a.4.4 0 0 1 .206.338z"/></svg>', + gitkeep: '<svg aria-hidden="true" fill="#F03C2E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.09 23.549a1.54 1.54 0 0 1-2.18 0L.451 13.089a1.54 1.54 0 0 1 0-2.179l7.191-7.19 2.733 2.733a1.85 1.85 0 0 0 .964 2.326v6.66a1.849 1.849 0 1 0 1.54 0V8.957l2.508 2.508a1.85 1.85 0 1 0 1.09-1.09l-2.634-2.634a1.85 1.85 0 0 0-2.378-2.377L8.73 2.63 10.91.451a1.54 1.54 0 0 1 2.179 0l10.459 10.46a1.54 1.54 0 0 1 0 2.179z"/></svg>', + ps1: '<svg aria-hidden="true" fill="#4EAA25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>', // PowerShell → 终端图标 + wxss: '<svg aria-hidden="true" fill="#663399" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63"/></svg>', // 小程序样式 → CSS 图标 + wxml: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z"/></svg>', // 小程序标记 → HTML 图标 + jsonl: '<svg aria-hidden="true" fill="#E34F26" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z"/></svg>', // JSON Lines → JSON 图标 + dockerignore: '<svg aria-hidden="true" fill="#2496ED" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>', // Docker 忽略 → Docker 图标 + png: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', svg: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', jpg: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', jpeg: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', gif: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', webp: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', bmp: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', ico: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', icns: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 13.5l2.5 3 3.5-4.5 4.5 6H5l4.5-5.5z"/></svg>', // 图片类 + lock: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-1V7a5 5 0 0 0-5-5zm-3 8V7a3 3 0 0 1 6 0v3H9z"/></svg>', // Cargo.lock 等 + conf: '<svg aria-hidden="true" fill="#9DB2C6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm7 1.5V7h3.5L13 3.5zM8 12h8v1.5H8V12zm0 3.5h8V17H8v-1.5z"/></svg>', // 配置文件 } /** 文件后缀 → 图标 HTML(v-html 安全注入)。 */ diff --git a/src/components/project/GitChanges.vue b/src/components/project/GitChanges.vue index 6e68dbc..17c0786 100644 --- a/src/components/project/GitChanges.vue +++ b/src/components/project/GitChanges.vue @@ -7,7 +7,7 @@ <span v-if="!showHistory" class="branch-count">{{ gitStatus?.changed_files.length ?? 0 }} {{ $t('gitChanges.changedFiles') }}</span> <span v-else class="branch-count">{{ totalCommits }} {{ $t('gitChanges.commitsUnit') }}</span> <button v-if="!showHistory" class="branch-switch-btn" @click="onSwitchBranch" :title="$t('gitChanges.switchBranch')"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg> </button> </div> @@ -85,7 +85,7 @@ <div v-else class="gc-history"> <!-- 提交搜索框(前端过滤已加载提交:subject/author) --> <div class="gc-search"> - <svg class="gc-search-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> + <svg aria-hidden="true" class="gc-search-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> <input v-model="commitSearch" class="gc-search-input" diff --git a/src/composables/ai/useAiApproval.ts b/src/composables/ai/useAiApproval.ts index b81ba06..6ca7272 100644 --- a/src/composables/ai/useAiApproval.ts +++ b/src/composables/ai/useAiApproval.ts @@ -153,9 +153,10 @@ async function batchApprove(decision: 'approve' | 'reject') { } } _batchProcessing.value = false - // 若有失败项,console.warn 诊断(用户可打开 DevTools 查看具体失败项) + // #14(2026-10-05):部分失败从静默 console.warn 升级为用户可见 toast(仍保留 warn 留痕诊断) if (failCount > 0) { console.warn(`[AI] 批量审批完成: ${successCount} 成功, ${failCount} 失败`) + void emit('ai-toast', { msg: t('aiTool.batchPartialFail', { ok: successCount, fail: failCount }), type: 'warning' }) } } diff --git a/src/composables/ai/useAiConversations.ts b/src/composables/ai/useAiConversations.ts index 318e4f6..22a3955 100644 --- a/src/composables/ai/useAiConversations.ts +++ b/src/composables/ai/useAiConversations.ts @@ -5,6 +5,7 @@ //! - newConversation/switchConversation 写 appSettings 持久化活跃会话 id import { ref } from 'vue' +import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' import { aiApi } from '@/api' import { useAppSettingsStore } from '@/stores/appSettings' import { state } from '@/stores/ai' @@ -15,12 +16,32 @@ import { nextMsgId, getConvState, clearConvStreamState, clearLastDelta, startApp import { t } from '@/i18n/i18n-helpers' import type { AiConversationDetail, AiMessage, AiToolCallInfo, AiToolCallStatus, ConvId, MessageId } from '@/api/types' +// ── V11c(2026-10-05)活跃会话 KV 按窗口域分流 ── +// 主窗与分离窗是独立 JS context,却共用同一 SQLite KV:两窗各自切会话都写 'df-ai-active-conv' +// 会互相覆盖(后写者赢),重启后两窗恢复到同一会话。按窗口 label 分流:主窗 'df-ai-active-conv' +// (历史 KV 兼容,主窗数据零迁移),分离窗 'df-ai-active-conv-detached';读端(loadConversations +// 启动恢复)与写端(切会话/新建/自动路由/删除回落)统一走本函数取键。 +// 每次调用现解析:窗口对象在 webview 启动即存在(getCurrentWebviewWindow 同步),无启动竞态; +// 非 Tauri 环境(单测/工具链)兜底主窗键。useAiEvents 的自动路由写入点同源 import 消费。 +export function activeConvKvKey(): string { + try { + return getCurrentWebviewWindow().label === 'main' + ? 'df-ai-active-conv' + : 'df-ai-active-conv-detached' + } catch { + return 'df-ai-active-conv' + } +} + const appSettings = useAppSettingsStore() /** 拉取会话列表;首次加载时若 appSettings 仍有有效 active-conv 且当前无活跃对话则恢复 */ async function loadConversations() { try { - state.conversations = await aiApi.listConversations() + // includeArchived=true:侧栏归档分组(ConversationSidebar archivedConvs)数据源即本内存列表, + // 若拉取时排除归档,重启后已归档会话不可见且无恢复入口(2026-10-05 实锤 bug)。 + // 活跃/归档分组由侧栏 groupedActive/archivedConvs 各自分离渲染;后端已分组截断,归档不挤占活跃名额。 + state.conversations = await aiApi.listConversations(50, true) // 虚拟项保底:活跃会话未落库(懒创建——首条消息前 DB 无记录)时,侧栏显示虚拟占位项。 // 后端 ai_conversation_create 仅生成 id 存内存不落库,首条消息发送后 save_conversation 才写库。 // 此处 active 会话不在 DB 列表时补一个虚拟项,实现「点新建立即出现在侧栏,不发消息不落库」。 @@ -42,7 +63,7 @@ async function loadConversations() { }) } // 恢复上次活跃对话(仅在首次加载、ID 仍有效且当前无活跃对话时) - const pending = appSettings.get<string | null>('df-ai-active-conv', null) + const pending = appSettings.get<string | null>(activeConvKvKey(), null) if (pending && !state.activeConversationId && state.conversations.some(c => c.id === pending)) { await switchConversation(pending) } @@ -101,7 +122,7 @@ async function newConversation() { ) if (!result) return // 失败已推气泡,保持当前视图 state.activeConversationId = result.id - void appSettings.set('df-ai-active-conv', result.id) + void appSettings.set(activeConvKvKey(), result.id) // G3.5:新对话无历史,load_more 游标复位 loadMoreCursor.hasMore = false loadMoreCursor.earliestSeq = null @@ -319,7 +340,7 @@ export async function switchConversation(id: string, force = false, opts?: { noC console.warn('[AI] switchConversation 失败(对话不存在),创建新对话:', id) const created = await aiApi.createConversation() if (mySwitchId !== _latestSwitchId) return 'ok' - void appSettings.set('df-ai-active-conv', created.id) + void appSettings.set(activeConvKvKey(), created.id) // 兜底置 activeId:此路径原只持久化不写 state.activeConversationId,旧(不存在)id 残留 // 会让后续所有事件被 isCurrent 判为非当前而丢弃或误路由 state.activeConversationId = created.id @@ -418,7 +439,7 @@ export async function switchConversation(id: string, force = false, opts?: { noC return 'ok' } // 切换提交完成才持久化活跃会话 + 重算 streaming(active 已指向目标 conv) - void appSettings.set('df-ai-active-conv', id) + void appSettings.set(activeConvKvKey(), id) setStreaming(targetGen, { convId: id, reason: 'switchConversation-recompute' }) // P2-2 完成卡回补:重启/分离窗回收后内存卡清零,按父 conv 反查子代理轨迹重建。 @@ -552,7 +573,7 @@ async function deleteConversation(id: string) { clearLastDelta(id) discardConvPendingApprovals(id) if (wasActive) { - void appSettings.remove('df-ai-active-conv') + void appSettings.remove(activeConvKvKey()) // 删除活跃会话 → load_more 游标复位(无 active 视图) loadMoreCursor.hasMore = false loadMoreCursor.earliestSeq = null diff --git a/src/composables/ai/useAiEvents.ts b/src/composables/ai/useAiEvents.ts index 62a8a57..0da107d 100644 --- a/src/composables/ai/useAiEvents.ts +++ b/src/composables/ai/useAiEvents.ts @@ -19,7 +19,7 @@ import { state } from '@/stores/ai' import { nextMsgId, setConvState, convStates, switchingConvs, getConvStreamState, setConvCurrentText, setConvStreaming, clearConvStreamState, clearAllApprovalTimers, clearAllToolSlowTimers, flushCurrentText, friendlyError, notifyConversationChanged, clearLastDelta, discardConvPendingApprovals, addConvAgentFinished, clearConvAgentFinished, type AgentFinishedStatus } from './aiShared' import { resetStreamWatchdog, clearAllStreamWatchdogs, clearResumeAttempts } from './useAiStream' import { setStreaming } from './streamingGuard' -import { scheduleConversationsRefresh } from './useAiConversations' +import { scheduleConversationsRefresh, activeConvKvKey } from './useAiConversations' import { pendingTaskParked, pendingTaskProgress } from './useAiPendingState' import { clearConvBoundaryCache } from './useTaskBoundary' import { handleStreamingEvent } from './useAiStreamingEvents' @@ -187,7 +187,7 @@ export function handleEvent(event: AiChatEvent) { // 首次收到事件时同步当前对话 id(后端自动建对话的场景)并持久化 if (convId && !state.activeConversationId) { state.activeConversationId = convId - void appSettings.set('df-ai-active-conv', convId) + void appSettings.set(activeConvKvKey(), convId) } // L2 状态机:AiConvStateChanged 是 per-conv 状态信号,在 isCurrent 守卫之前处理 // (切走会话时也须更新 conv_state,防停止按钮/MaxRoundsCard 错态) diff --git a/src/composables/ai/useAiSend.ts b/src/composables/ai/useAiSend.ts index a425a26..e524ca3 100644 --- a/src/composables/ai/useAiSend.ts +++ b/src/composables/ai/useAiSend.ts @@ -14,7 +14,7 @@ //! - drainQueue 由 handleEvent(AiCompleted) 经 ai-drain-queue 事件总线触发(非直接调用, //! 打破 events↔send 循环依赖);本模块模块级监听该事件 -import { listen } from '@tauri-apps/api/event' +import { listen, emit } from '@tauri-apps/api/event' import { invoke } from '@tauri-apps/api/core' import { ref } from 'vue' import { aiApi } from '@/api' @@ -537,6 +537,12 @@ async function stopChat() { // 决策 a(逐条续发):不再清队列,保留供 AiCompleted 链式触发 drainQueue 续发。 // 后端 agentic/mod.rs emit AiCompleted 注释明确「保证前端收事件时后端已可接下一条,队列续发不被拒」; // 当前未完成回复已入库(agentic/mod.rs save_conversation)保留为截断回复。 + // I8(2026-10-05):停止≠清队,用户预期常是「全部停下」——队列非空时一次性提示 + // 「停止后 N 条排队消息仍将自动发送」,给用户明确预期(要全停可逐条取消/清队列)。 + const pendingQueued = state.queue.filter(q => q.conversationId === state.activeConversationId).length + if (pendingQueued > 0) { + void emit('ai-toast', { msg: t('ai.queueWillContinue', { n: pendingQueued }), type: 'info' }) + } try { // F-260616-09 B 批4(决策 e):传 activeConversationId,停止仅作用于当前 conv。 await aiApi.stopChat(state.activeConversationId) diff --git a/src/composables/ai/useAiToolEvents.ts b/src/composables/ai/useAiToolEvents.ts index 94747f6..15f8014 100644 --- a/src/composables/ai/useAiToolEvents.ts +++ b/src/composables/ai/useAiToolEvents.ts @@ -25,10 +25,19 @@ export function handleToolEvent(event: AiChatEvent): boolean { } // 同 id 重复 Started 不重复 push(provider id 不稳,防残留 running 空卡) if (!findToolCall(event.id)) { + // #15(2026-10-05):末条非 assistant(如 system 占位/思考 pill/完成轨迹折叠重排)时 + // 原直接丢弃工具卡 → running 卡消失但工具仍在执行。兜底向前找最近 assistant 落卡; + // 全程无 assistant(极端:首轮工具前无任何回复)才真丢弃(与原行为一致)。 const lastMsg = state.messages[state.messages.length - 1] - if (lastMsg && lastMsg.role === 'assistant') { - lastMsg.toolCalls = lastMsg.toolCalls || [] - lastMsg.toolCalls.push(info) + let target = (lastMsg && lastMsg.role === 'assistant') ? lastMsg : null + if (!target) { + for (let i = state.messages.length - 1; i >= 0; i--) { + if (state.messages[i].role === 'assistant') { target = state.messages[i]; break } + } + } + if (target) { + target.toolCalls = target.toolCalls || [] + target.toolCalls.push(info) } } // 工具开始执行即挂慢执行计时器(超时仅 toast,不动 running 态) diff --git a/src/composables/ai/useAiWindow.ts b/src/composables/ai/useAiWindow.ts index 0d4eab7..1bc64ed 100644 --- a/src/composables/ai/useAiWindow.ts +++ b/src/composables/ai/useAiWindow.ts @@ -12,6 +12,7 @@ import { invoke } from '@tauri-apps/api/core' import { state } from '@/stores/ai' +import { useAppSettingsStore } from '@/stores/appSettings' import { nextMsgId, getConvState, convStates } from './aiShared' import type { MessageId } from '@/api/types' import { setStreaming } from './streamingGuard' @@ -58,6 +59,45 @@ export async function emitDetachedOpenConversation(convId: string): Promise<void } } +// ── V11b(2026-10-05)跨窗收回分离窗:reattachPanel 的 UI 入口 ── +// 病灶:reattachPanel(关闭全部分离窗+回主面板)全仓零 UI 调用——分离窗内「关闭」按钮走 +// closeDetachedWindow(仅关窗不恢复主面板),用户想收回只能手动重开主面板。主/分离窗是 +// 独立 JS context,分离窗按钮无法直调主窗 store;走 Tauri 全局事件广播(对齐 OPEN_CONV_EVENT +// 模式):分离窗 emit → 主窗(label 判定)监听 → 执行 reattachPanel。 +const REATTACH_EVENT = 'ai-reattach-panel' + +/** 发起方(分离窗)侧:广播收回事件,主窗口消费 */ +export async function emitReattachToMain(): Promise<void> { + try { + const { emit } = await import('@tauri-apps/api/event') + await emit(REATTACH_EVENT, {}) + } catch (e) { + console.error('[AI] 跨窗收回事件发送失败:', e) + } +} + +/** 承接方(主窗口)侧:注册收回监听。handler=reattachPanel(由 AiChat 挂载时注入,归窗口域) */ +let _reattachListenerRegistered = false +export function registerMainReattachListener(handler: () => Promise<void>): void { + if (_reattachListenerRegistered) return + _reattachListenerRegistered = true + void (async () => { + try { + const [{ listen }, { getCurrentWebviewWindow }] = await Promise.all([ + import('@tauri-apps/api/event'), + import('@tauri-apps/api/webviewWindow'), + ]) + if (getCurrentWebviewWindow().label !== 'main') return + await listen<Record<string, never>>(REATTACH_EVENT, async () => { + await handler() + }) + } catch (e) { + console.error('[AI] 主窗口收回监听注册失败:', e) + _reattachListenerRegistered = false + } + })() +} + /** 承接方(分离窗)侧:注册跨窗切会话监听,切换完成后聚焦本窗口(点击发生在主窗口, * 焦点须跟到承接会话的窗口,否则用户感知「点了没反应」)。 * handler 由 useAiConversations 注入:切会话逻辑归会话域,避免本模块反向依赖造成模块环。 */ @@ -114,6 +154,12 @@ async function detachPanel(convId?: string) { localStorage.setItem(textKey(targetConv), state.currentText) } } + // V11c: 种子分离窗活跃会话 KV——分离窗 AiChat 挂载同样跑 loadConversations(), + // 读按窗口域分流的 'df-ai-active-conv-detached'(独立 JS context 不与主窗共 pinia state), + // 不种子则分离窗启动回落列表首条而非主窗正看的会话。目标 conv 为空(无活跃会话)跳过。 + if (targetConv) { + void useAppSettingsStore().set('df-ai-active-conv-detached', targetConv) + } // 主窗口 state 与分离窗口 state 独立(各自 webview 独立 JS context, // stores/ai.ts 模块级单例仅在同 webview 内共享),清主窗口 state 不影响分离窗口生成态。 // 分离窗口接管生成后,主窗口不应再持该会话生成态残留(防重开面板时 UI 显生成中幽灵/进度条)。 @@ -133,7 +179,10 @@ async function detachPanel(convId?: string) { minHeight: 400, center: true, decorations: true, - alwaysOnTop: true, // 分离窗口默认置顶(AI 助手常态:用户在主窗口工作时希望 AI 辅助窗口可见) + // V11a(2026-10-05):默认不置顶——原 true 会让分离窗盖住其他应用(VSCode/浏览器)妨碍工作; + // 用户开过置顶的偏好走 AiChat.vue 的 df-ai-always-on-top KV 恢复链(分离窗挂载时 + // setAlwaysOnTop(saved)),默认改 false 不影响已开置顶用户,只改变首次/未开者的初始态。 + alwaysOnTop: false, }) win.once('tauri://created', () => { state.panelOpen = false @@ -183,6 +232,8 @@ async function reattachPanel() { state.docked = false state.panelOpen = true persistUiState() + // V11c: 清分离窗活跃会话 KV(分离窗已全关,残留无意义;下次 detachPanel 会重新种子) + void useAppSettingsStore().remove('df-ai-active-conv-detached') // 时序加固:reattach 关闭分离窗口后,显式停分离窗口残留 listener(分离窗口 close 异步, // onBeforeUnmount stopListener 可能延迟),防主面板重新挂载 startListener 时与旧 listener 重叠竞态。 stopListener() diff --git a/src/composables/ai/useStreamRenderer.ts b/src/composables/ai/useStreamRenderer.ts index 54cbcff..08f933a 100644 --- a/src/composables/ai/useStreamRenderer.ts +++ b/src/composables/ai/useStreamRenderer.ts @@ -15,7 +15,7 @@ //! 与 TaskDetail/AiChat/MessageList 同实例,渲染结果零变化。 import { ref, computed, nextTick, type Ref, type ComputedRef } from 'vue' -import { useMarkdown } from '../useMarkdown' +import { useMarkdown, wrapNakedDiff } from '../useMarkdown' import { useAppSettingsStore } from '../../stores/appSettings' /// 流式渲染返回的单块渲染结果(html + 模板 v-for key) @@ -122,7 +122,9 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { const purify = getPurify() if (!marked || !purify) return escapeFallback(block) try { - return purify.sanitize(marked.parse(block) as string) + // R8:流式块同源裸 diff 预处理(原仅完成态 renderMd 有),流式中裸 +/- 行即红绿代码块, + // 不再等完成态突变丢阅读锚点。守卫3(围栏内不动)保证与完成态 renderMd 幂等不双重包裹。 + return purify.sanitize(marked.parse(wrapNakedDiff(block)) as string) } catch { return escapeFallback(block) } @@ -150,7 +152,8 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { // AR-1 流式 MD 开关关 → 回退纯文本短路(escapeFallback 全文转义 + <br>,原行为) // mdReady 未就绪也走纯文本兜底(marked 尚未加载完成)。 if (!streamingMdEnabled.value || !mdReady.value || !getMarked() || !getPurify()) { - _prevStreamText = '' // 纯文本降级路径不增量缓存(下帧回全量,防旧帧基线误命中) + // 纯文本降级路径不增量缓存(下帧回全量,防旧帧基线误命中)→ 三元组清零走统一入口(DRY) + resetIncrementalBaseline() return [{ html: escapeFallback(text), key: 'fallback' }] } // 增量切块:追加场景复用上一帧已完成块,只对「旧末块 + 增长段」增量 lexer。 @@ -159,13 +162,10 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { let blocks: string[] if (appendOnly) { const growth = text.slice(_prevStreamText.length) - const combined = _prevTail + growth - const combinedBlocks = splitBlocks(combined) - blocks = combinedBlocks.length <= 1 - // 仍单块增长(旧末块未跨边界)→ 已完成块不变,新末块 = 旧末块+增长 - ? _prevCompleted.concat(combinedBlocks) - // 旧末块跨边界拆出若干已完成块(围栏闭合/新 \n\n)+ 新末块 - : _prevCompleted.concat(combinedBlocks.slice(0, -1), combinedBlocks[combinedBlocks.length - 1]) + // splitBlocks 输出无论 1 块(旧末块未跨边界,新末块=旧末块+增长)还是 N 块 + //(旧末块跨边界拆出若干已完成块+新末块),语义均为「追加到已完成块序列之后」, + // 原 if/else 两分支 concat 结果恒等价 → 收敛单 concat(死分支清除,DRY)。 + blocks = _prevCompleted.concat(splitBlocks(_prevTail + growth)) } else { blocks = splitBlocks(text) } @@ -240,6 +240,14 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { }) } + /// 三元组增量基线清零(唯一写入口):纯文本降级路径/清流式状态/清块缓存三处共用。 + /// 手抄三行式已废弃,清基线必须走本函数(DRY 2026-10-05)。 + function resetIncrementalBaseline(): void { + _prevStreamText = '' + _prevCompleted = [] + _prevTail = '' + } + /// 清理流式渲染状态(streaming 翻 false / 会话切换 / 卸载前调) /// 取消 pending rAF + 清 streamingBlocks + 重置 lastStreamText + 清增量基线。 function clearStreamingState(): void { @@ -247,9 +255,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { streamingBlocks.value = [] lastStreamText = '' // 清增量基线(防新轮/新会话误命中旧帧已完成块)。 - _prevStreamText = '' - _prevCompleted = [] - _prevTail = '' + resetIncrementalBaseline() } /// 仅取消 pending rAF(不清 streamingBlocks/lastStreamText)。 @@ -263,9 +269,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { /// 同步清增量基线(已完成块引用旧会话内容,防跨会话误复用)。 function resetStreamCache(): void { _blockCache.clear() - _prevStreamText = '' - _prevCompleted = [] - _prevTail = '' + resetIncrementalBaseline() } return { diff --git a/src/composables/useMarkdown.ts b/src/composables/useMarkdown.ts index a3741bc..0434063 100644 --- a/src/composables/useMarkdown.ts +++ b/src/composables/useMarkdown.ts @@ -205,6 +205,10 @@ function renderMd(text: string): string { * 本函数在 marked.parse 前扫描文本,把「连续 ≥2 行以 + 或 - 开头、且至少含 1 行 +」的 * 段落整段包成 ```diff 围栏代码块,复用②子项已就绪的 hljs diff 红绿样式,零额外 CSS。 * + * R8(2026-10-05)改 export:流式渲染链(useStreamRenderer.renderStreamingBlocks)同源复用, + * 治「流式中裸 diff 渲染为普通段落,完成后突变红绿代码块」的阅读锚点丢失。 + * 幂等安全:守卫3(围栏内不动)保证流式已包的块,完成态 renderMd 再处理不双重包裹。 + * * 守卫: * 1. **纯 - 列表不触发**: 普通 markdown ul 每行 `- ` 是常见语法,纯 - 会误伤成代码块。 * 故要求段内至少含 1 行 `+ `(+ 必须含),这是裸 diff 区别于普通列表的强特征。 @@ -221,7 +225,7 @@ const _DIFF_LINE_RE = /^[ \t]*[+-][^\n]*$/ // 行首可选空格/tab + +/- + 任 const _PLUS_LINE_RE = /^[ \t]*\+[^\n]*$/ // 含 + 的行(+ 必须含的判定) const _FENCE_OPEN_RE = /^[ \t]{0,3}(```|~~~)/ // marked 围栏开头(行首 ≤3 空格 + 3+ 反引号/波浪) -function wrapNakedDiff(text: string): string { +export function wrapNakedDiff(text: string): string { if (!text) return text // 快速短路:全文无 +/- 行首特征直接返回原文(避免大文本白跑切段) if (!/[+-]/.test(text)) return text diff --git a/src/i18n/en/ai.ts b/src/i18n/en/ai.ts index 59c0786..9b2e326 100644 --- a/src/i18n/en/ai.ts +++ b/src/i18n/en/ai.ts @@ -27,7 +27,7 @@ export default { aiStreamRetry: 'Connection interrupted, auto-retrying ({attempt}/{max})…', // Fatal errors (4xx/auth) are not retryable — prompt user to check settings instead notRetryable: 'This error cannot be resolved by retrying. Please check the configuration.', - streamInterruptedAfterTool: '⚠ The tool finished, but the following reply was interrupted (no data stream for a long time). You can click Continue to retry.', + streamInterruptedAfterTool: '⚠ The tool finished, but the following reply was interrupted (no data stream for a long time). Click Retry to continue (reruns the whole turn; completed tools will execute again).', streamInterrupted: '⚠ The response was interrupted (no data stream for a long time). This may be due to a network disconnection or a backend error. Please try again.', // Light-convergence notice when the watchdog probe reports the backend already finished (only the closing event was lost) streamFinishedConverged: 'The response was interrupted during finalization and has been converged.', @@ -45,6 +45,7 @@ export default { queuedSendFailed: 'Failed to send queued message: {error}', // UX-P2-A2: toast when queued messages get dropped by error/regenerate/edit (was silent) queueCleared: 'Cleared {n} queued message(s)', + queueWillContinue: 'Stopped. {n} queued message(s) will still send automatically.', approvalTimeout: '⚰ Approval timed out, automatically rejected: {toolName}', // conversation load/switch failure feedback (network jitter/IPC down → empty list/blank, was silent) loadConvFail: 'Failed to load conversations. Please check your network or backend connection.', @@ -181,6 +182,9 @@ export default { echoDirs: '{n} dirs', echoBudget: 'budget {rounds} rounds / {tokens} tokens / {secs}s', echoNoBudget: 'no budget limit', + // A12: budget validation field locator; A11: dirty-edit kept notice + budgetInvalidField: '{field} must be a positive integer', + dirtyKept: 'Unsaved edits kept (target conversation switched, verify before confirming)', }, }, } diff --git a/src/i18n/en/aiChat.ts b/src/i18n/en/aiChat.ts index d752981..75ed977 100644 --- a/src/i18n/en/aiChat.ts +++ b/src/i18n/en/aiChat.ts @@ -49,6 +49,19 @@ export default { pinOnTop: 'Pin on top', unpin: 'Unpin', closeWindow: 'Close window', + // I9(2026-10-05) text/code file attachment + pickFileTitle: 'Select text/code files', + textFileFilter: 'Text/code files', + fileTooLargeTruncated: 'File too large, truncated on insert', + fileReadFailed: 'Failed to read file, skipped', + shortcutsTitle: 'Keyboard shortcuts', + scNewConv: 'New conversation', + scFocusInput: 'Focus message input', + scRegenerate: 'Regenerate last reply', + scClearChat: 'Clear current conversation', + scSearch: 'Focus conversation search', + scToggleSidebar: 'Toggle sidebar', + scCloseOverlay: 'Close dialog / panel', // ── Approval overlay (global top-right) ── approvalWaitingTitle: 'Approval required', @@ -163,9 +176,10 @@ export default { copyAsImageFailed: 'Failed to copy as image', // ── Message actions bar ── - regenerate: 'Regenerate', - regenerateFailed: 'Regenerate failed: {msg}', + // regenerateUnavailable: Ctrl+R / retry paths still call store.regenerate(); precheck toast key + // (I6 revert removed the UI button only — key still referenced by useAiSend.ts, review 2026-10-05 ②) regenerateUnavailable: 'No reply to regenerate', + regenerateFailed: 'Regenerate failed: {msg}', // fetch_url "Install obscura via AI" button triggers sendMessage; failure toast (mirrors editMessageFailed) sendFailed: 'Send failed: {msg}', @@ -237,6 +251,8 @@ export default { compressing: 'Compressing…', clearSuccess: 'Context cleared', compressSuccess: 'Context compressed', + // B4: one-shot auto-compress notice (loop auto-compress no longer fully silent) + autoCompressed: 'Context auto-compressed (earlier messages folded into summary)', compressError: 'Compression failed: {msg}', clearError: 'Clear failed: {msg}', // Separator expand / collapse @@ -279,6 +295,10 @@ export default { tokenInLabel: 'Input (cache miss, full price)', tokenCacheLabel: 'Cache hit (low price)', tokenOutLabel: 'Output', + // #12 conversation-cumulative tokens (token detail popover footer) + tokenConvTotal: 'Conversation total', + tokenConvTotalIn: 'Total in', + tokenConvTotalOut: 'Total out', tokenReasonLabel: 'Reasoning', tokenCacheRateLabel: 'Cache hit rate', tokenModelLabel: 'Model', diff --git a/src/i18n/en/aiTool.ts b/src/i18n/en/aiTool.ts index 2b59686..a7e0d74 100644 --- a/src/i18n/en/aiTool.ts +++ b/src/i18n/en/aiTool.ts @@ -14,6 +14,10 @@ export default { argsCollapse: 'Collapse args', // batch approve high-risk interception (count badge + confirm text) batchHighRiskHint: '{n} high-risk', + // R7 group-header failure badge: folded group contains failed tool cards + groupFailCount: '{n} failed', + // #14 batch approve partial failure toast + batchPartialFail: 'Batch done: {ok} approved, {fail} failed.', batchHighRiskConfirm: 'Batch approval includes {n} high-risk operations (e.g. delete/command execution/network requests). Approve all?', rejectedHint: 'User rejected this action', // tool execution slow hint (frontend fallback, no backend cancel IPC) @@ -30,6 +34,8 @@ export default { // Singular expand/collapse (ToolCard single-card content, command output toggle) expand: 'Expand', collapse: 'Collapse', + diffExpand: 'Expand all {n} lines', + diffCollapse: 'Collapse diff', written: 'written', items: 'items', lines: 'lines', diff --git a/src/i18n/zh-CN/ai.ts b/src/i18n/zh-CN/ai.ts index f264a9c..a6cc162 100644 --- a/src/i18n/zh-CN/ai.ts +++ b/src/i18n/zh-CN/ai.ts @@ -27,7 +27,7 @@ export default { aiStreamRetry: '与上游连接中断,自动重试中({attempt}/{max})…', // Fatal 类错误(4xx/鉴权)不可重试,提示用户去设置而非重试 notRetryable: '此错误无法通过重试解决,请检查配置', - streamInterruptedAfterTool: '⚠ 工具已执行完成,后续回复中断(长时间无数据流)。可点继续重试。', + streamInterruptedAfterTool: '⚠ 工具已执行完成,后续回复中断(长时间无数据流)。可点「重试」续跑(整轮重跑,已完成工具会重新执行)。', streamInterrupted: '⚠ 响应中断(长时间无数据流)。可能是网络断连或后端异常,请重试。', // 看门狗探测到后端已正常收敛(仅收尾事件丢失)时补的轻收敛提示,非错误语气 streamFinishedConverged: '响应在收尾阶段中断,已自动完成收敛。', @@ -45,6 +45,7 @@ export default { queuedSendFailed: '队列消息发送失败:{error}', // UX-P2-A2:出错/重生成/编辑清排队消息的 toast 提示(原静默丢弃无任何反馈) queueCleared: '已清 {n} 条排队消息', + queueWillContinue: '已停止,{n} 条排队消息仍将自动发送', approvalTimeout: '⚰ 审批超时,已自动拒绝:{toolName}', // 对话加载/切换失败反馈(网络抖动/IPC 断开 → 列表空/切换空白,原静默无反馈) loadConvFail: '对话列表加载失败,请检查网络或后端连接', @@ -176,6 +177,9 @@ export default { echoDirs: '{n} 个目录', echoBudget: '预算 {rounds} 轮 / {tokens} tokens / {secs}s', echoNoBudget: '预算不限', + // A12: 预算校验错误定位字段;A11: 脏编辑保留提示 + budgetInvalidField: '{field} 需为正整数', + dirtyKept: '有未提交的编辑已保留(目标会话已切换,确认前请核对)', }, }, } diff --git a/src/i18n/zh-CN/aiChat.ts b/src/i18n/zh-CN/aiChat.ts index 6409895..6027f93 100644 --- a/src/i18n/zh-CN/aiChat.ts +++ b/src/i18n/zh-CN/aiChat.ts @@ -49,6 +49,19 @@ export default { pinOnTop: '置顶', unpin: '取消置顶', closeWindow: '关闭窗口', + // I9(2026-10-05)文本/代码文件附件 + pickFileTitle: '选择文本/代码文件', + textFileFilter: '文本/代码文件', + fileTooLargeTruncated: '文件过大,已截断插入', + fileReadFailed: '文件读取失败,已跳过', + shortcutsTitle: '键盘快捷键', + scNewConv: '新建对话', + scFocusInput: '聚焦输入框', + scRegenerate: '重新生成回复', + scClearChat: '清空当前对话', + scSearch: '聚焦会话搜索', + scToggleSidebar: '展开/收起侧栏', + scCloseOverlay: '关闭弹窗/面板', // ── 审批浮层(全局右上角)── approvalWaitingTitle: '有待审批请求', @@ -164,9 +177,10 @@ export default { copyAsImageFailed: '复制为图片失败', // ── 消息操作栏── - regenerate: '重新生成', - regenerateFailed: '重新生成失败:{msg}', + // regenerateUnavailable:Ctrl+R / 重试链仍走 store.regenerate(),前置校验失败 toast 用 + // (I6 回滚只撤了 UI 按钮,此键仍被 useAiSend.ts 引用——review 2026-10-05 ②) regenerateUnavailable: '没有可重新生成的回复', + regenerateFailed: '重新生成失败:{msg}', // fetch_url "AI 帮装 obscura" 按钮触发 sendMessage 失败的 toast(对齐 editMessageFailed 模式) sendFailed: '发送失败:{msg}', @@ -238,6 +252,8 @@ export default { compressing: '压缩中…', clearSuccess: '上下文已清空', compressSuccess: '上下文已压缩', + // B4: 自动压缩完成一次性提示(loop 自动压缩不再全程静默) + autoCompressed: '上下文已自动压缩(较早消息已折叠为摘要)', compressError: '压缩失败:{msg}', clearError: '清空失败:{msg}', // 折叠条展开 / 收起 @@ -280,6 +296,10 @@ export default { tokenInLabel: '输入(未命中,全价)', tokenCacheLabel: '缓存命中(低价)', tokenOutLabel: '输出', + // #12 会话累计 token(token 详情面板尾部段) + tokenConvTotal: '会话累计', + tokenConvTotalIn: '会话累计·输入', + tokenConvTotalOut: '会话累计·输出', tokenReasonLabel: '思考(reasoning)', tokenCacheRateLabel: '缓存命中率', tokenModelLabel: '模型', diff --git a/src/i18n/zh-CN/aiTool.ts b/src/i18n/zh-CN/aiTool.ts index 9221b49..6ac9bad 100644 --- a/src/i18n/zh-CN/aiTool.ts +++ b/src/i18n/zh-CN/aiTool.ts @@ -14,6 +14,10 @@ export default { argsCollapse: '收起参数', // 批量批准 High 风险拦截(计数徽标 + 二次确认文案) batchHighRiskHint: '含 {n} 项高风险', + // R7 组头失败徽标:折叠组内含失败工具卡 + groupFailCount: '{n} 失败', + // #14 批量审批部分失败提示 + batchPartialFail: '批量完成:{ok} 项成功,{fail} 项失败', batchHighRiskConfirm: '批量批准包含 {n} 项高风险操作(如删除/命令执行/网络请求),确认全部放行?', rejectedHint: '用户拒绝了此操作', // 工具执行超时提示(前端降级,无后端取消 IPC) @@ -29,6 +33,8 @@ export default { clickToCopy: '点击复制完整路径', collapse: '收起', expand: '展开', + diffExpand: '展开全部 {n} 行', + diffCollapse: '收起 diff', written: '已写入', items: '项', lines: '行', diff --git a/src/views/ApprovalPopup.vue b/src/views/ApprovalPopup.vue index 9ec8cf8..14eedec 100644 --- a/src/views/ApprovalPopup.vue +++ b/src/views/ApprovalPopup.vue @@ -12,7 +12,7 @@ <!-- 顶部拖动条(双击不最大化,因 decorations:false) --> <div class="popup-header" data-tauri-drag-region @mousedown="onDragStart" @mouseup="onDragEnd"> <div class="popup-header-icon"> - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M9 11l3 3L22 4" /> <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" /> </svg> @@ -20,7 +20,7 @@ </div> <div class="popup-header-title">{{ $t('aiChat.popupTitle') }}</div> <button class="popup-header-close" :title="$t('common.close')" @click.stop="onClose"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18" /> <line x1="6" y1="6" x2="18" y2="18" /> </svg> @@ -71,7 +71,7 @@ @click.stop="onApproveOnce(tc)" > <span v-if="processingId === tc.id" class="popup-btn-spinner"></span> - <svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthOnce') }}<span class="popup-btn-key">1</span> </button> <button @@ -79,7 +79,7 @@ :disabled="processingId === tc.id" @click.stop="onApproveSession(tc)" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthSession') }} </button> <button @@ -87,7 +87,7 @@ :disabled="processingId === tc.id" @click.stop="onApproveAlways(tc)" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiChat.dirAuthAlways') }}<span class="popup-btn-key">2</span> </button> <button @@ -95,7 +95,7 @@ :disabled="processingId === tc.id" @click.stop="onReject(tc)" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ $t('aiChat.dirAuthDeny') }}<span class="popup-btn-key">3</span> </button> </div> @@ -106,7 +106,7 @@ @click.stop="onApprove(tc)" > <span v-if="processingId === tc.id" class="popup-btn-spinner"></span> - <svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> + <svg aria-hidden="true" v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> {{ $t('aiTool.approve') }}<span class="popup-btn-key">1</span> </button> <button @@ -114,7 +114,7 @@ :disabled="processingId === tc.id" @click.stop="onReject(tc)" > - <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> + <svg aria-hidden="true" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> {{ $t('aiTool.reject') }}<span class="popup-btn-key">3</span> </button> </div> @@ -123,7 +123,7 @@ <!-- 底部:打开审批面板(聚焦主窗口) --> <button class="popup-footer" @click="onActivate"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="M21 3l-7 7" /><path d="M3 21l7-7" /> </svg> {{ $t('aiChat.popupOpenPanel') }} diff --git a/src/views/AuditLog.vue b/src/views/AuditLog.vue index 9557e65..c07633a 100644 --- a/src/views/AuditLog.vue +++ b/src/views/AuditLog.vue @@ -4,7 +4,7 @@ <h1>{{ t('auditLog.title') }}</h1> <div class="header-actions"> <button class="btn btn-ghost" @click="reloadAudit" :disabled="loading" v-if="activeTab === 'audit'"> - <svg v-if="loading" class="audit-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> + <svg aria-hidden="true" v-if="loading" class="audit-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> {{ t('auditLog.refresh') }} </button> </div> @@ -13,11 +13,11 @@ <!-- Tab 导航 --> <nav class="detail-tabs audit-tabs"> <button class="tab-btn" :class="{ 'tab-active': activeTab === 'audit' }" type="button" @click="switchTab('audit')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> {{ t('auditLog.tabAudit') }} </button> <button class="tab-btn" :class="{ 'tab-active': activeTab === 'conversations' }" type="button" @click="switchTab('conversations')"> - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg> + <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg> {{ t('auditLog.tabConversations') }} </button> </nav> @@ -43,7 +43,7 @@ <!-- 状态条 --> <div v-if="loading" class="empty-state audit-loading"> - <svg class="audit-spinner audit-spinner--lg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> + <svg aria-hidden="true" class="audit-spinner audit-spinner--lg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> </div> <div v-else-if="errorMsg" class="empty-state audit-error">⚠ {{ errorMsg }}</div> <div v-else-if="filteredRecords.length === 0" class="empty-state">{{ t('auditLog.empty') }}</div> diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index dfa0241..7d1fde2 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -8,8 +8,8 @@ </div> <div class="dash-header-right"> <button class="btn btn-ghost btn-sm dash-refresh-btn" @click="refresh" :disabled="loading" :title="$t('dashboard.refresh')"> - <svg v-if="loading" class="dash-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> - <svg v-else width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> + <svg aria-hidden="true" v-if="loading" class="dash-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> + <svg aria-hidden="true" v-else width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> </button> </div> </header> diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue index 7f1865e..99e311e 100644 --- a/src/views/ProjectDetail.vue +++ b/src/views/ProjectDetail.vue @@ -130,7 +130,7 @@ <!-- 标题行操作入口 --> <div class="module-manager-header"> <button class="btn btn-primary btn-sm" type="button" @click="openAddModule"> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> + <svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> {{ $t('fileExplorer.addModule') }} </button> <button @@ -171,10 +171,10 @@ <span class="module-path-overview" :title="m.path">{{ m.path }}</span> <span class="module-row-actions"> <button class="ai-btn-icon" :title="$t('fileExplorer.editModule')" @click="openEditModule(m)"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> </button> <button class="ai-btn-icon module-row-action-danger" :title="$t('fileExplorer.deleteCurrentModule')" @click="confirmRemoveModule(m)"> - <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> + <svg aria-hidden="true" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> </button> </span> </div>