feat: Add batch delete functionality for chat sessions (#480)

* feat: add batch delete functionality for chat sessions

Backend:
- Add batchRemove controller to handle bulk session deletion
- Add POST /api/hermes/sessions/batch-delete endpoint
- Support both local session store and CLI deletion
- Return detailed results (deleted, failed, errors)

Frontend:
- Add batch selection mode with checkboxes in SessionListItem
- Add batch selection toggle and select all button
- Add batch delete button with confirmation
- Update ChatPanel to manage selected session IDs
- Add batchDeleteSessions API function

i18n:
- Add batch delete translations for all 8 languages
- Simplify "Web UI/API Server Sessions" to "Sessions"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: vertically align buttons in session list header

Add inline-flex and center alignment to all buttons in session-list-actions
to ensure proper vertical centering with the title text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: ensure proper vertical alignment in session list header

- Set fixed height of 22px for session-list-actions
- Add min-height and height to all buttons
- Add line-height to session-list-title for text baseline alignment
- Add min-height: 0 to session-list-header to prevent flex stretch

This ensures the title and all action buttons are perfectly vertically centered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: call loadSessions after batch delete instead of looping deleteSession

The previous implementation was calling chatStore.deleteSession(id) in a loop
after batch delete API succeeded, which triggered individual delete API calls
for each session - causing n API requests instead of 1.

Now we simply call loadSessions() to refresh the session list from the server
after successful batch deletion, ensuring:
- Only 1 API request for batch delete
- UI stays in sync with server state
- No duplicate API calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: improve update mechanism reliability

Major improvements to the update system:

**Path Resolution:**
- Remove unreliable dirname(process.execPath) assumption
- Use npm from PATH environment variable
- Dynamically get global prefix via `npm prefix -g`
- Calculate CLI path based on actual global install location

**Windows Support:**
- Remove complex cmd.exe wrapper logic
- Directly call npm.cmd (works on all Windows setups)
- Simplified quote handling

**Error Handling:**
- Add fallback error message (err.stderr || err.message || String(err))
- Add default success message when output is empty
- Wrap spawnRestart in try-finally to ensure cleanup

**Timing:**
- Increase timeout from 120s to 10min (slow network support)
- Increase restart delay from 2s to 3s (safer margin)

**Code Quality:**
- Remove unused functions (getNodeBinDir, getWindowsShell, quoteForWindowsCommand)
- Use constants instead of magic numbers (10 * 60 * 1000)
- More maintainable and cross-platform compatible

This fixes issues where updates would fail due to incorrect npm/CLI paths
on systems with non-standard Node.js installations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-05-06 16:15:42 +08:00
committed by GitHub
parent d13423b9dd
commit 266f6e1a59
14 changed files with 342 additions and 49 deletions
@@ -284,6 +284,54 @@ export async function remove(ctx: any) {
ctx.body = { ok: true }
}
export async function batchRemove(ctx: any) {
const { ids } = ctx.request.body as { ids?: string[] }
if (!ids || !Array.isArray(ids) || ids.length === 0) {
ctx.status = 400
ctx.body = { error: 'ids is required and must be a non-empty array' }
return
}
const validIds = ids.filter(id => typeof id === 'string' && id.trim() !== '')
if (validIds.length === 0) {
ctx.status = 400
ctx.body = { error: 'No valid session ids provided' }
return
}
const results = {
deleted: 0,
failed: 0,
errors: [] as Array<{ id: string; error: string }>
}
if (useLocalSessionStore()) {
for (const id of validIds) {
const ok = localDeleteSession(id)
if (ok) {
deleteUsage(id)
results.deleted++
} else {
results.failed++
results.errors.push({ id, error: 'Failed to delete session' })
}
}
} else {
for (const id of validIds) {
const ok = await hermesCli.deleteSession(id)
if (ok) {
deleteUsage(id)
results.deleted++
} else {
results.failed++
results.errors.push({ id, error: 'Failed to delete session' })
}
}
}
ctx.body = { ...results, ok: true }
}
export async function usageBatch(ctx: any) {
const ids = (ctx.query.ids as string)
if (!ids) {