mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-01 17:10:24 +08:00
Compare commits
1 Commits
fix/zoom-r
...
fix/idb-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
084c390d7c |
@@ -585,7 +585,7 @@ export default function ChatPanel({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const currentSession = sessionManager.currentSession
|
const currentSession = sessionManager.currentSession
|
||||||
if (currentSession && currentSession.messages.length > 0) {
|
if (currentSession) {
|
||||||
// Restore from session manager (IndexedDB)
|
// Restore from session manager (IndexedDB)
|
||||||
justLoadedSessionRef.current = true
|
justLoadedSessionRef.current = true
|
||||||
syncUIWithSession(currentSession)
|
syncUIWithSession(currentSession)
|
||||||
@@ -622,7 +622,7 @@ export default function ChatPanel({
|
|||||||
lastSyncedSessionIdRef.current = newSessionId
|
lastSyncedSessionIdRef.current = newSessionId
|
||||||
|
|
||||||
// Sync UI with new session
|
// Sync UI with new session
|
||||||
if (newSession && newSession.messages.length > 0) {
|
if (newSession) {
|
||||||
justLoadedSessionRef.current = true
|
justLoadedSessionRef.current = true
|
||||||
syncUIWithSession(newSession)
|
syncUIWithSession(newSession)
|
||||||
} else if (!newSession) {
|
} else if (!newSession) {
|
||||||
|
|||||||
@@ -47,6 +47,33 @@ interface ChatSessionDB extends DBSchema {
|
|||||||
|
|
||||||
// Database singleton
|
// Database singleton
|
||||||
let dbPromise: Promise<IDBPDatabase<ChatSessionDB>> | null = null
|
let dbPromise: Promise<IDBPDatabase<ChatSessionDB>> | null = null
|
||||||
|
const resetDBPromise = () => {
|
||||||
|
dbPromise = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isClosingError = (error: unknown): boolean => {
|
||||||
|
return (
|
||||||
|
error instanceof DOMException &&
|
||||||
|
error.name === "InvalidStateError" &&
|
||||||
|
/closing/i.test(error.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const withDB = async <T>(
|
||||||
|
action: (db: IDBPDatabase<ChatSessionDB>) => Promise<T>,
|
||||||
|
): Promise<T> => {
|
||||||
|
try {
|
||||||
|
const db = await getDB()
|
||||||
|
return await action(db)
|
||||||
|
} catch (error) {
|
||||||
|
if (isClosingError(error)) {
|
||||||
|
resetDBPromise()
|
||||||
|
const db = await getDB()
|
||||||
|
return await action(db)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getDB(): Promise<IDBPDatabase<ChatSessionDB>> {
|
async function getDB(): Promise<IDBPDatabase<ChatSessionDB>> {
|
||||||
if (!dbPromise) {
|
if (!dbPromise) {
|
||||||
@@ -60,7 +87,23 @@ async function getDB(): Promise<IDBPDatabase<ChatSessionDB>> {
|
|||||||
}
|
}
|
||||||
// Future migrations: if (oldVersion < 2) { ... }
|
// Future migrations: if (oldVersion < 2) { ... }
|
||||||
},
|
},
|
||||||
|
terminated() {
|
||||||
|
resetDBPromise()
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
dbPromise
|
||||||
|
.then((db) => {
|
||||||
|
db.onversionchange = () => {
|
||||||
|
db.close()
|
||||||
|
resetDBPromise()
|
||||||
|
}
|
||||||
|
db.onclose = () => {
|
||||||
|
resetDBPromise()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
resetDBPromise()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return dbPromise
|
return dbPromise
|
||||||
}
|
}
|
||||||
@@ -79,27 +122,29 @@ export function isIndexedDBAvailable(): boolean {
|
|||||||
export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
||||||
if (!isIndexedDBAvailable()) return []
|
if (!isIndexedDBAvailable()) return []
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
return await withDB(async (db) => {
|
||||||
const tx = db.transaction(STORE_NAME, "readonly")
|
const tx = db.transaction(STORE_NAME, "readonly")
|
||||||
const index = tx.store.index("by-updated")
|
const index = tx.store.index("by-updated")
|
||||||
const metadata: SessionMetadata[] = []
|
const metadata: SessionMetadata[] = []
|
||||||
|
|
||||||
// Use cursor to read only metadata fields (avoids loading full messages/XML)
|
// Use cursor to read only metadata fields (avoids loading full messages/XML)
|
||||||
let cursor = await index.openCursor(null, "prev") // newest first
|
let cursor = await index.openCursor(null, "prev") // newest first
|
||||||
while (cursor) {
|
while (cursor) {
|
||||||
const s = cursor.value
|
const s = cursor.value
|
||||||
metadata.push({
|
metadata.push({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
title: s.title,
|
title: s.title,
|
||||||
createdAt: s.createdAt,
|
createdAt: s.createdAt,
|
||||||
updatedAt: s.updatedAt,
|
updatedAt: s.updatedAt,
|
||||||
messageCount: s.messages.length,
|
messageCount: s.messages.length,
|
||||||
hasDiagram: !!s.diagramXml && s.diagramXml.trim().length > 0,
|
hasDiagram:
|
||||||
thumbnailDataUrl: s.thumbnailDataUrl,
|
!!s.diagramXml && s.diagramXml.trim().length > 0,
|
||||||
})
|
thumbnailDataUrl: s.thumbnailDataUrl,
|
||||||
cursor = await cursor.continue()
|
})
|
||||||
}
|
cursor = await cursor.continue()
|
||||||
return metadata
|
}
|
||||||
|
return metadata
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get session metadata:", error)
|
console.error("Failed to get session metadata:", error)
|
||||||
return []
|
return []
|
||||||
@@ -109,8 +154,9 @@ export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
|||||||
export async function getSession(id: string): Promise<ChatSession | null> {
|
export async function getSession(id: string): Promise<ChatSession | null> {
|
||||||
if (!isIndexedDBAvailable()) return null
|
if (!isIndexedDBAvailable()) return null
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
return await withDB(async (db) => {
|
||||||
return (await db.get(STORE_NAME, id)) || null
|
return (await db.get(STORE_NAME, id)) || null
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get session:", error)
|
console.error("Failed to get session:", error)
|
||||||
return null
|
return null
|
||||||
@@ -120,8 +166,9 @@ export async function getSession(id: string): Promise<ChatSession | null> {
|
|||||||
export async function saveSession(session: ChatSession): Promise<boolean> {
|
export async function saveSession(session: ChatSession): Promise<boolean> {
|
||||||
if (!isIndexedDBAvailable()) return false
|
if (!isIndexedDBAvailable()) return false
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
await withDB(async (db) => {
|
||||||
await db.put(STORE_NAME, session)
|
await db.put(STORE_NAME, session)
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle quota exceeded
|
// Handle quota exceeded
|
||||||
@@ -133,8 +180,9 @@ export async function saveSession(session: ChatSession): Promise<boolean> {
|
|||||||
await deleteOldestSession()
|
await deleteOldestSession()
|
||||||
// Retry once
|
// Retry once
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
await withDB(async (db) => {
|
||||||
await db.put(STORE_NAME, session)
|
await db.put(STORE_NAME, session)
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (retryError) {
|
} catch (retryError) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -153,8 +201,9 @@ export async function saveSession(session: ChatSession): Promise<boolean> {
|
|||||||
export async function deleteSession(id: string): Promise<void> {
|
export async function deleteSession(id: string): Promise<void> {
|
||||||
if (!isIndexedDBAvailable()) return
|
if (!isIndexedDBAvailable()) return
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
await withDB(async (db) => {
|
||||||
await db.delete(STORE_NAME, id)
|
await db.delete(STORE_NAME, id)
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete session:", error)
|
console.error("Failed to delete session:", error)
|
||||||
}
|
}
|
||||||
@@ -163,8 +212,9 @@ export async function deleteSession(id: string): Promise<void> {
|
|||||||
export async function getSessionCount(): Promise<number> {
|
export async function getSessionCount(): Promise<number> {
|
||||||
if (!isIndexedDBAvailable()) return 0
|
if (!isIndexedDBAvailable()) return 0
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
return await withDB(async (db) => {
|
||||||
return await db.count(STORE_NAME)
|
return await db.count(STORE_NAME)
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get session count:", error)
|
console.error("Failed to get session count:", error)
|
||||||
return 0
|
return 0
|
||||||
@@ -174,14 +224,15 @@ export async function getSessionCount(): Promise<number> {
|
|||||||
export async function deleteOldestSession(): Promise<void> {
|
export async function deleteOldestSession(): Promise<void> {
|
||||||
if (!isIndexedDBAvailable()) return
|
if (!isIndexedDBAvailable()) return
|
||||||
try {
|
try {
|
||||||
const db = await getDB()
|
await withDB(async (db) => {
|
||||||
const tx = db.transaction(STORE_NAME, "readwrite")
|
const tx = db.transaction(STORE_NAME, "readwrite")
|
||||||
const index = tx.store.index("by-updated")
|
const index = tx.store.index("by-updated")
|
||||||
const cursor = await index.openCursor()
|
const cursor = await index.openCursor()
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
await cursor.delete()
|
await cursor.delete()
|
||||||
}
|
}
|
||||||
await tx.done
|
await tx.done
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete oldest session:", error)
|
console.error("Failed to delete oldest session:", error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user