feat: streaming progress for bulk sync - real-time updates

This commit is contained in:
2026-04-30 23:09:35 +08:00
parent e38a2fabe7
commit 583486de1a
3 changed files with 248 additions and 106 deletions
+91 -22
View File
@@ -114,7 +114,7 @@ export function GamesClient() {
})
try {
// Use the bulk sync endpoint with selected game IDs
// Use the bulk sync endpoint with streaming progress
const res = await fetch("/api/games/sync/bulk", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -129,17 +129,52 @@ export function GamesClient() {
throw new Error(errorData?.error || `Sync failed: HTTP ${res.status}`)
}
const data = await res.json()
// Read streaming response
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
setSyncProgress((prev) => ({
...prev,
isRunning: false,
synced: data.synced || 0,
failed: data.failed || 0,
currentGame: null,
}))
setSyncCompleted(true)
setSelectedIds(new Set())
if (reader) {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split("\n")
buffer = lines.pop() || ""
for (const line of lines) {
if (!line.trim()) continue
try {
const data = JSON.parse(line)
if (data.type === "progress") {
setSyncProgress((prev) => ({
...prev,
current: data.current,
total: data.total,
synced: data.synced,
failed: data.failed,
currentGame: data.currentGame,
}))
} else if (data.type === "complete") {
setSyncProgress((prev) => ({
...prev,
isRunning: false,
total: data.total,
synced: data.synced,
failed: data.failed,
currentGame: null,
}))
setSyncCompleted(true)
setSelectedIds(new Set())
}
} catch (e) {
// Ignore parse errors
}
}
}
}
} catch (error) {
console.error("Sync selected failed:", error)
alert(`Sync failed: ${error instanceof Error ? error.message : String(error)}`)
@@ -169,7 +204,7 @@ export function GamesClient() {
})
try {
// Use the bulk sync endpoint (processes in parallel)
// Use the bulk sync endpoint with streaming progress
const res = await fetch("/api/games/sync/bulk", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -181,17 +216,51 @@ export function GamesClient() {
throw new Error(errorData?.error || `Sync failed: HTTP ${res.status}`)
}
const data = await res.json()
// Read streaming response
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
setSyncProgress((prev) => ({
...prev,
isRunning: false,
total: data.total || 0,
synced: data.synced || 0,
failed: data.failed || 0,
currentGame: null,
}))
setSyncCompleted(true)
if (reader) {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split("\n")
buffer = lines.pop() || ""
for (const line of lines) {
if (!line.trim()) continue
try {
const data = JSON.parse(line)
if (data.type === "progress") {
setSyncProgress((prev) => ({
...prev,
current: data.current,
total: data.total,
synced: data.synced,
failed: data.failed,
currentGame: data.currentGame,
}))
} else if (data.type === "complete") {
setSyncProgress((prev) => ({
...prev,
isRunning: false,
total: data.total,
synced: data.synced,
failed: data.failed,
currentGame: null,
}))
setSyncCompleted(true)
}
} catch (e) {
// Ignore parse errors
}
}
}
}
} catch (error) {
console.error("Sync all failed:", error)
alert(`Sync failed: ${error instanceof Error ? error.message : String(error)}`)