mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-02 01:20:23 +08:00
Compare commits
3 Commits
fix/ai-mod
...
fix/electr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7525253499 | ||
|
|
384221c498 | ||
|
|
7877b39fa1 |
@@ -11,10 +11,16 @@ import {
|
||||
} from "@/components/ui/resizable"
|
||||
import { useDiagram } from "@/contexts/diagram-context"
|
||||
import { i18n, type Locale } from "@/lib/i18n/config"
|
||||
import { isIndexedDBUsable } from "@/lib/session-storage"
|
||||
|
||||
export default function Home() {
|
||||
const { drawioRef, handleDiagramExport, onDrawioLoad, resetDrawioReady } =
|
||||
useDiagram()
|
||||
const {
|
||||
drawioRef,
|
||||
handleDiagramExport,
|
||||
handleDiagramAutoSave,
|
||||
onDrawioLoad,
|
||||
resetDrawioReady,
|
||||
} = useDiagram()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
// Extract current language from pathname (e.g., "/zh/about" → "zh")
|
||||
@@ -26,6 +32,7 @@ export default function Home() {
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [isDrawioReady, setIsDrawioReady] = useState(false)
|
||||
const [isElectron, setIsElectron] = useState(false)
|
||||
const [canPersist, setCanPersist] = useState(false)
|
||||
const [drawioBaseUrl, setDrawioBaseUrl] = useState(
|
||||
process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net",
|
||||
)
|
||||
@@ -76,6 +83,10 @@ export default function Home() {
|
||||
setDrawioBaseUrl(`${window.location.origin}/drawio/index.html`)
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
const usable = await isIndexedDBUsable()
|
||||
setCanPersist(usable)
|
||||
})()
|
||||
setIsLoaded(true)
|
||||
}, [pathname, router])
|
||||
|
||||
@@ -84,6 +95,17 @@ export default function Home() {
|
||||
onDrawioLoad()
|
||||
}, [onDrawioLoad])
|
||||
|
||||
const handleDrawioAutoSave = useCallback(
|
||||
(data: { xml?: string }) => {
|
||||
handleDiagramAutoSave(data)
|
||||
// Only suppress modified state when persistence is available
|
||||
if (canPersist) {
|
||||
drawioRef.current?.status({ message: "", modified: false })
|
||||
}
|
||||
},
|
||||
[canPersist, drawioRef, handleDiagramAutoSave],
|
||||
)
|
||||
|
||||
const handleDarkModeChange = () => {
|
||||
const newValue = !darkMode
|
||||
setDarkMode(newValue)
|
||||
@@ -174,13 +196,25 @@ export default function Home() {
|
||||
<DrawIoEmbed
|
||||
key={`${drawioUi}-${darkMode}-${currentLang}-${isElectron}`}
|
||||
ref={drawioRef}
|
||||
autosave
|
||||
onAutoSave={handleDrawioAutoSave}
|
||||
onExport={handleDiagramExport}
|
||||
onLoad={handleDrawioLoad}
|
||||
baseUrl={drawioBaseUrl}
|
||||
configuration={
|
||||
canPersist
|
||||
? { confirmExit: false }
|
||||
: undefined
|
||||
}
|
||||
urlParameters={{
|
||||
ui: drawioUi,
|
||||
spin: false,
|
||||
libraries: false,
|
||||
// Disable modified tracking only when persistence is available
|
||||
...(canPersist && {
|
||||
modified: false,
|
||||
keepmodified: false,
|
||||
}),
|
||||
saveAndExit: false,
|
||||
noSaveBtn: true,
|
||||
noExitBtn: true,
|
||||
|
||||
@@ -137,26 +137,22 @@ export default function ChatPanel({
|
||||
const onFetchChart = (saveToHistory = true) => {
|
||||
return Promise.race([
|
||||
new Promise<string>((resolve) => {
|
||||
if (resolverRef && "current" in resolverRef) {
|
||||
resolverRef.current = resolve
|
||||
}
|
||||
resolverRef.current = resolve
|
||||
if (saveToHistory) {
|
||||
onExport()
|
||||
} else {
|
||||
handleExportWithoutHistory()
|
||||
}
|
||||
}),
|
||||
new Promise<string>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
"Chart export timed out after 10 seconds",
|
||||
),
|
||||
),
|
||||
10000,
|
||||
),
|
||||
),
|
||||
new Promise<string>((_, reject) => {
|
||||
const currentResolver = resolverRef.current
|
||||
setTimeout(() => {
|
||||
if (resolverRef.current === currentResolver) {
|
||||
resolverRef.current = null
|
||||
}
|
||||
reject(new Error("Chart export timed out after 10 seconds"))
|
||||
}, 10000)
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -677,7 +673,7 @@ export default function ChatPanel({
|
||||
// Debounce: save after 1 second of no changes
|
||||
localStorageDebounceRef.current = setTimeout(async () => {
|
||||
try {
|
||||
if (messages.length > 0) {
|
||||
if (messages.length > 0 || hasDiagramNow) {
|
||||
const sessionData = await buildSessionData({
|
||||
// Only capture thumbnail if there was a diagram AND this isn't a no-diagram session
|
||||
withThumbnail: hasDiagramNow && !isNodiagramSession,
|
||||
@@ -699,6 +695,7 @@ export default function ChatPanel({
|
||||
}
|
||||
}
|
||||
}, [
|
||||
chartXML,
|
||||
messages,
|
||||
status,
|
||||
sessionIsAvailable,
|
||||
@@ -729,7 +726,8 @@ export default function ChatPanel({
|
||||
const handleVisibilityChange = async () => {
|
||||
if (
|
||||
document.visibilityState === "hidden" &&
|
||||
messagesRef.current.length > 0
|
||||
(messagesRef.current.length > 0 ||
|
||||
isRealDiagram(chartXMLRef.current))
|
||||
) {
|
||||
try {
|
||||
// Attempt to save session - browser may not wait for completion
|
||||
|
||||
@@ -20,9 +20,10 @@ interface DiagramContextType {
|
||||
loadDiagram: (chart: string, skipValidation?: boolean) => string | null
|
||||
handleExport: () => void
|
||||
handleExportWithoutHistory: () => void
|
||||
resolverRef: React.Ref<((value: string) => void) | null>
|
||||
drawioRef: React.Ref<DrawIoEmbedRef | null>
|
||||
resolverRef: React.MutableRefObject<((value: string) => void) | null>
|
||||
drawioRef: React.MutableRefObject<DrawIoEmbedRef | null>
|
||||
handleDiagramExport: (data: any) => void
|
||||
handleDiagramAutoSave: (data: { xml?: string }) => void
|
||||
clearDiagram: () => void
|
||||
saveDiagramToFile: (
|
||||
filename: string,
|
||||
@@ -267,6 +268,11 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDiagramAutoSave = (data: { xml?: string }) => {
|
||||
if (!data?.xml) return
|
||||
setChartXML(data.xml)
|
||||
}
|
||||
|
||||
const clearDiagram = () => {
|
||||
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
||||
// Skip validation for trusted internal template (loadDiagram also sets chartXML)
|
||||
@@ -391,6 +397,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
||||
resolverRef,
|
||||
drawioRef,
|
||||
handleDiagramExport,
|
||||
handleDiagramAutoSave,
|
||||
clearDiagram,
|
||||
saveDiagramToFile,
|
||||
getThumbnailSvg,
|
||||
|
||||
@@ -75,6 +75,18 @@ export function isIndexedDBAvailable(): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if IndexedDB is actually usable (not just present).
|
||||
export async function isIndexedDBUsable(): Promise<boolean> {
|
||||
if (!isIndexedDBAvailable()) return false
|
||||
try {
|
||||
const db = await getDB()
|
||||
db.close()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CRUD Operations
|
||||
export async function getAllSessionMetadata(): Promise<SessionMetadata[]> {
|
||||
if (!isIndexedDBAvailable()) return []
|
||||
|
||||
Reference in New Issue
Block a user