Files
picoclaw/web/frontend/src/hooks/use-session-history.ts
T
wenjie dea06c391c feat(web): add agent management UI and improve launcher integration (#1358)
* Improve the web launcher and gateway integration across backend and frontend.

- add runtime model availability checks for local and OAuth-backed models
- support launcher-driven gateway host overrides and websocket URL resolution
- add gateway log clearing and keep incremental log sync consistent after resets
- migrate session history APIs to JSONL metadata-backed storage with legacy fallback
- expose session titles and improve chat history loading and error handling
- move shared backend runtime helpers into the web utils package
- avoid blocking web startup when automatic onboard initialization fails
- add backend tests covering gateway readiness, host resolution, models, logs, and sessions

* feat(agent): add skills and tools management APIs and UI

- add backend APIs to list, view, import, and delete skills
- add tool status and toggle endpoints with dependency-aware config updates
- add agent skills/tools pages, routes, sidebar entries, and i18n strings
- add backend tests for the new skills and tools flows

* chore(frontend): upgrade shadcn to 4.0.5 and refresh lockfile

* chore(web): keep backend dist placeholder tracked
2026-03-11 18:37:00 +08:00

113 lines
2.9 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react"
import { useTranslation } from "react-i18next"
import { type SessionSummary, deleteSession, getSessions } from "@/api/sessions"
const LIMIT = 20
interface UseSessionHistoryOptions {
activeSessionId: string
onDeletedActiveSession: () => void
}
export function useSessionHistory({
activeSessionId,
onDeletedActiveSession,
}: UseSessionHistoryOptions) {
const { t } = useTranslation()
const observerRef = useRef<HTMLDivElement>(null)
const [sessions, setSessions] = useState<SessionSummary[]>([])
const [offset, setOffset] = useState(0)
const [hasMore, setHasMore] = useState(true)
const [isLoadingMore, setIsLoadingMore] = useState(false)
const [loadError, setLoadError] = useState(false)
const loadSessions = useCallback(
async (reset = true) => {
try {
const currentOffset = reset ? 0 : offset
if (reset) {
setLoadError(false)
setHasMore(true)
setOffset(0)
}
const data = await getSessions(currentOffset, LIMIT)
setLoadError(false)
if (data.length < LIMIT) {
setHasMore(false)
}
if (reset) {
setSessions(data)
} else {
setSessions((prev) => {
const existingIds = new Set(prev.map((s) => s.id))
const newItems = data.filter((s) => !existingIds.has(s.id))
return [...prev, ...newItems]
})
}
setOffset(currentOffset + data.length)
} catch (err) {
console.error("Failed to fetch session history:", err)
setLoadError(true)
if (!reset) {
setHasMore(false)
}
} finally {
setIsLoadingMore(false)
}
},
[offset],
)
useEffect(() => {
if (!observerRef.current || !hasMore || isLoadingMore || loadError) return
const observer = new IntersectionObserver(
(entries) => {
if (
entries[0].isIntersecting &&
hasMore &&
!isLoadingMore &&
!loadError
) {
setIsLoadingMore(true)
void loadSessions(false)
}
},
{ threshold: 0.1 },
)
observer.observe(observerRef.current)
return () => observer.disconnect()
}, [hasMore, isLoadingMore, loadError, loadSessions])
const handleDeleteSession = useCallback(
async (id: string) => {
try {
await deleteSession(id)
setSessions((prev) => prev.filter((s) => s.id !== id))
if (id === activeSessionId) {
onDeletedActiveSession()
}
} catch (err) {
console.error("Failed to delete session:", err)
}
},
[activeSessionId, onDeletedActiveSession],
)
return {
sessions,
hasMore,
loadError,
loadErrorMessage: t("chat.historyLoadFailed"),
observerRef,
loadSessions,
handleDeleteSession,
}
}