新增: 文件浏览器增强(窗口分离/Git 变更/行号/Diff/分页提交历史)

- 窗口分离: FileExplorer 可弹出独立 Tauri 窗口
- 行号显示: 文件预览左侧显示行号列
- 按后缀图标: rs/ts/js/vue/css/html 等 20+ 类型彩色图标
- Diff 视图: 有 Git 变更的文件可切换 diff 红绿视图
- Git 变更面板: 变更文件列表(按目录缩进分组)+提交历史(分页加载)
- 提交详情: 点击提交查看变更文件列表及文件级 diff
- 时间显示: 提交历史显示相对时间/具体日期
- 面包屑导航不丢预览: 导航时不改变当前预览文件
- 刷新保留选中文件: 只清树缓存不清预览
- 中文编码修复: git 命令注入 LANG/LC_ALL 环境变量
- 自适应布局: 弹性 flex 布局,窄窗口适配
- 滚动条优化: 内容区内部滚动,不溢出页面层
- 多工程管理: 工具栏添加工程按钮+弹窗表单
- 审批加载超时缩短: 130s 降至 30s
- 文件变更自动刷新: write_file/patch_file 触发 df-data-changed
This commit is contained in:
2026-06-29 19:25:30 +08:00
parent 63936e3016
commit dfed588b54
20 changed files with 1812 additions and 159 deletions

View File

@@ -10,15 +10,15 @@
import { ref, reactive, computed, watch, onBeforeUnmount, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useConfirm } from '@/composables/useConfirm'
import { STREAM_TIMEOUT_MS } from '@/composables/ai/useAiStream'
import type { AiToolCallInfo } from '@/api/types'
/**
* 审批按钮 loading 超时兜底(B-260616-08)。
* 后端回事件使 tc.status 离开 pending_approval → watch 复位。
* 兜底计时到点复位 + toast 提示(对齐全局看门狗 STREAM_TIMEOUT_MS,后端无回执时给用户重试入口)。
* 兜底计时到点复位 + toast 提示(后端 IPC 应在 1~3s 内返回;30s 兜底远足超时后允许用户重试)。
* 与 stream 看门狗(130s)解耦:审批是按钮级交互,不是后台流,不需要对齐后端 120s 空闲超时。
*/
const APPROVE_LOADING_TIMEOUT_MS = STREAM_TIMEOUT_MS
const APPROVE_LOADING_TIMEOUT_MS = 30_000
/** AE-2025-05:High 风险工具白名单(后端 tool_registry.rs RiskLevel::High 对齐) */
const HIGH_RISK_TOOLS = new Set<string>([

View File

@@ -0,0 +1,76 @@
/**
* 文件浏览器窗口分离 — 弹出/分离/附加 FileExplorer 独立窗口。
*
* 对标 useAiWindow 的 detach/reattach 模式,复用 WebviewWindow API。
* 分离窗口以独立 Tauri 窗口打开 FileExplorerDetached 视图,
* 通过 URL query `?projectId=xxx` 传参。
*/
const DETACHED_LABEL_PREFIX = 'fe-detached-'
function detachedLabel(projectId: string): string {
return DETACHED_LABEL_PREFIX + projectId
}
/**
* 将文件浏览器弹出到独立窗口。
* 若该项目的窗口已存在则聚焦,不重复创建。
*
* @param projectId 项目 id(传给分离窗口加载工程列表)
* @param title 窗口标题(可选,默认"文件浏览器")
*/
export async function detachFileExplorer(
projectId: string,
title?: string,
): Promise<void> {
if (!projectId) return
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
const label = detachedLabel(projectId)
// 已存在则聚焦
const existing = await WebviewWindow.getByLabel(label)
if (existing) {
await existing.setFocus()
return
}
const url =
window.location.origin +
window.location.pathname +
`#/file-explorer-detached?projectId=${encodeURIComponent(projectId)}`
const win = new WebviewWindow(label, {
url,
title: title || '文件浏览器',
width: 900,
height: 600,
minWidth: 640,
minHeight: 400,
center: true,
decorations: true,
})
win.once('tauri://created', () => {
console.log('[FileExplorer] 分离窗口已创建:', label)
})
win.once('tauri://error', (e) => {
console.error('[FileExplorer] 窗口创建失败:', e)
})
}
/**
* 关闭指定项目的文件浏览器分离窗口。
*/
export async function reattachFileExplorer(projectId: string): Promise<void> {
if (!projectId) return
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
const label = detachedLabel(projectId)
const existing = await WebviewWindow.getByLabel(label)
if (existing) {
try {
await existing.close()
} catch {
// 忽略关闭失败
}
}
}