mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
优化会话中最近项目列表加载慢
优化会话中最近项目列表加载慢
This commit is contained in:
parent
eddaec547e
commit
4e59c2feb5
@ -41,6 +41,7 @@ import {
|
||||
SessionBranchingError,
|
||||
} from '../../utils/sessionBranching.js'
|
||||
import { registerChangedFileAccessRoot, registerFilesystemAccessRoot } from '../services/filesystemAccessRoots.js'
|
||||
import { findGitRoot } from '../../utils/git.js'
|
||||
import { traceCaptureService, trimTraceCallPreviews } from '../services/traceCaptureService.js'
|
||||
|
||||
const workspaceService = new WorkspaceService(
|
||||
@ -949,14 +950,14 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
||||
const validSessions = sessions.filter((session) => session.workDirExists && session.workDir)
|
||||
|
||||
// First pass: group by logical project root so worktrees stay under the same project.
|
||||
// Optimization: prefer s.projectRoot (already resolved by listSessions) and only fall back
|
||||
// to the expensive getSessionWorkDir (reads the full transcript) when projectRoot is absent.
|
||||
const realPathMap = new Map<string, { projectPath: string; modifiedAt: string; sessionCount: number; sessionId: string }>()
|
||||
const fallbackSessionIds: string[] = []
|
||||
for (const s of validSessions) {
|
||||
let realPath: string
|
||||
try {
|
||||
const workDir = await sessionService.getSessionWorkDir(s.id)
|
||||
realPath = s.projectRoot || workDir || sessionService.desanitizePath(s.projectPath)
|
||||
} catch {
|
||||
realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
|
||||
const realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
|
||||
if (!s.projectRoot && s.id) {
|
||||
fallbackSessionIds.push(s.id)
|
||||
}
|
||||
|
||||
const existing = realPathMap.get(realPath)
|
||||
@ -972,7 +973,46 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve fallback sessions in parallel (only those missing projectRoot)
|
||||
if (fallbackSessionIds.length > 0) {
|
||||
const resolvedPaths = await Promise.all(
|
||||
fallbackSessionIds.map(async (sessionId) => {
|
||||
try {
|
||||
const workDir = await sessionService.getSessionWorkDir(sessionId)
|
||||
return { sessionId, workDir }
|
||||
} catch {
|
||||
return { sessionId, workDir: null as string | null }
|
||||
}
|
||||
}),
|
||||
)
|
||||
for (const { sessionId, workDir } of resolvedPaths) {
|
||||
if (!workDir) continue
|
||||
// Find the entry we already inserted with the desanitized projectPath
|
||||
const session = validSessions.find((s) => s.id === sessionId)
|
||||
const oldKey = session?.projectRoot || sessionService.desanitizePath(session?.projectPath || '')
|
||||
const oldEntry = oldKey ? realPathMap.get(oldKey) : undefined
|
||||
const newRealPath = workDir
|
||||
if (oldKey && oldEntry && oldKey !== newRealPath) {
|
||||
// Migrate entry to the resolved real path
|
||||
realPathMap.delete(oldKey)
|
||||
const existingNew = realPathMap.get(newRealPath)
|
||||
if (!existingNew || oldEntry.modifiedAt > (existingNew?.modifiedAt ?? '')) {
|
||||
realPathMap.set(newRealPath, {
|
||||
projectPath: newRealPath,
|
||||
modifiedAt: oldEntry.modifiedAt,
|
||||
sessionCount: oldEntry.sessionCount + (existingNew?.sessionCount ?? 0),
|
||||
sessionId: oldEntry.sessionId,
|
||||
})
|
||||
} else {
|
||||
existingNew.sessionCount += oldEntry.sessionCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build project list with git info — parallelize git operations
|
||||
// Optimization: use findGitRoot (fs.stat traversal + LRU cache, <5ms) to skip git spawns
|
||||
// on non-git directories, avoiding slow git rev-parse on each (seconds per non-git dir).
|
||||
const entries = Array.from(realPathMap.entries())
|
||||
const projects = await Promise.all(
|
||||
entries.map(async ([realPath, info]) => {
|
||||
@ -981,15 +1021,11 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
||||
let isGit = false
|
||||
let repoName: string | null = null
|
||||
let branch: string | null = null
|
||||
try {
|
||||
const proc = Bun.spawn(['git', 'rev-parse', '--is-inside-work-tree'], {
|
||||
cwd: realPath, stdout: 'pipe', stderr: 'pipe',
|
||||
})
|
||||
const out = await new Response(proc.stdout).text()
|
||||
isGit = out.trim() === 'true'
|
||||
|
||||
if (isGit) {
|
||||
// Run branch + remote in parallel
|
||||
const gitRoot = findGitRoot(realPath)
|
||||
if (gitRoot) {
|
||||
isGit = true
|
||||
// Run branch + remote in parallel
|
||||
try {
|
||||
const [branchResult, remoteResult] = await Promise.all([
|
||||
(async () => {
|
||||
const branchProc = Bun.spawn(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], {
|
||||
@ -1010,8 +1046,9 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
||||
])
|
||||
branch = isDesktopWorktreeBranchName(branchResult) ? null : branchResult
|
||||
repoName = remoteResult
|
||||
}
|
||||
} catch { /* not a git repo or dir doesn't exist */ }
|
||||
} catch { /* git command failed */ }
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
projectPath: info.projectPath, realPath, projectName, isGit, repoName, branch,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user