mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
优化会话中最近项目列表加载慢
优化会话中最近项目列表加载慢
This commit is contained in:
parent
eddaec547e
commit
4e59c2feb5
@ -41,6 +41,7 @@ import {
|
|||||||
SessionBranchingError,
|
SessionBranchingError,
|
||||||
} from '../../utils/sessionBranching.js'
|
} from '../../utils/sessionBranching.js'
|
||||||
import { registerChangedFileAccessRoot, registerFilesystemAccessRoot } from '../services/filesystemAccessRoots.js'
|
import { registerChangedFileAccessRoot, registerFilesystemAccessRoot } from '../services/filesystemAccessRoots.js'
|
||||||
|
import { findGitRoot } from '../../utils/git.js'
|
||||||
import { traceCaptureService, trimTraceCallPreviews } from '../services/traceCaptureService.js'
|
import { traceCaptureService, trimTraceCallPreviews } from '../services/traceCaptureService.js'
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService(
|
const workspaceService = new WorkspaceService(
|
||||||
@ -949,14 +950,14 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
|||||||
const validSessions = sessions.filter((session) => session.workDirExists && session.workDir)
|
const validSessions = sessions.filter((session) => session.workDirExists && session.workDir)
|
||||||
|
|
||||||
// First pass: group by logical project root so worktrees stay under the same project.
|
// 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 realPathMap = new Map<string, { projectPath: string; modifiedAt: string; sessionCount: number; sessionId: string }>()
|
||||||
|
const fallbackSessionIds: string[] = []
|
||||||
for (const s of validSessions) {
|
for (const s of validSessions) {
|
||||||
let realPath: string
|
const realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
|
||||||
try {
|
if (!s.projectRoot && s.id) {
|
||||||
const workDir = await sessionService.getSessionWorkDir(s.id)
|
fallbackSessionIds.push(s.id)
|
||||||
realPath = s.projectRoot || workDir || sessionService.desanitizePath(s.projectPath)
|
|
||||||
} catch {
|
|
||||||
realPath = s.projectRoot || sessionService.desanitizePath(s.projectPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = realPathMap.get(realPath)
|
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
|
// 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 entries = Array.from(realPathMap.entries())
|
||||||
const projects = await Promise.all(
|
const projects = await Promise.all(
|
||||||
entries.map(async ([realPath, info]) => {
|
entries.map(async ([realPath, info]) => {
|
||||||
@ -981,15 +1021,11 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
|||||||
let isGit = false
|
let isGit = false
|
||||||
let repoName: string | null = null
|
let repoName: string | null = null
|
||||||
let branch: string | null = null
|
let branch: string | null = null
|
||||||
try {
|
const gitRoot = findGitRoot(realPath)
|
||||||
const proc = Bun.spawn(['git', 'rev-parse', '--is-inside-work-tree'], {
|
if (gitRoot) {
|
||||||
cwd: realPath, stdout: 'pipe', stderr: 'pipe',
|
isGit = true
|
||||||
})
|
// Run branch + remote in parallel
|
||||||
const out = await new Response(proc.stdout).text()
|
try {
|
||||||
isGit = out.trim() === 'true'
|
|
||||||
|
|
||||||
if (isGit) {
|
|
||||||
// Run branch + remote in parallel
|
|
||||||
const [branchResult, remoteResult] = await Promise.all([
|
const [branchResult, remoteResult] = await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
const branchProc = Bun.spawn(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], {
|
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
|
branch = isDesktopWorktreeBranchName(branchResult) ? null : branchResult
|
||||||
repoName = remoteResult
|
repoName = remoteResult
|
||||||
}
|
} catch { /* git command failed */ }
|
||||||
} catch { /* not a git repo or dir doesn't exist */ }
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectPath: info.projectPath, realPath, projectName, isGit, repoName, branch,
|
projectPath: info.projectPath, realPath, projectName, isGit, repoName, branch,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user