mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
Compare commits
2 Commits
493ee168b1
...
fix/flash-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cc7a74084 | ||
|
|
77a2f6f6fa |
@@ -35,6 +35,7 @@ export default function Home() {
|
||||
const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min")
|
||||
const [darkMode, setDarkMode] = useState(false)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [isDrawioReady, setIsDrawioReady] = useState(false)
|
||||
const [closeProtection, setCloseProtection] = useState(false)
|
||||
|
||||
const chatPanelRef = useRef<ImperativePanelHandle>(null)
|
||||
@@ -104,12 +105,18 @@ export default function Home() {
|
||||
setIsLoaded(true)
|
||||
}, [pathname, router])
|
||||
|
||||
const handleDrawioLoad = useCallback(() => {
|
||||
setIsDrawioReady(true)
|
||||
onDrawioLoad()
|
||||
}, [onDrawioLoad])
|
||||
|
||||
const handleDarkModeChange = async () => {
|
||||
await saveDiagramToStorage()
|
||||
const newValue = !darkMode
|
||||
setDarkMode(newValue)
|
||||
localStorage.setItem("next-ai-draw-io-dark-mode", String(newValue))
|
||||
document.documentElement.classList.toggle("dark", newValue)
|
||||
setIsDrawioReady(false)
|
||||
resetDrawioReady()
|
||||
}
|
||||
|
||||
@@ -118,6 +125,7 @@ export default function Home() {
|
||||
const newUi = drawioUi === "min" ? "sketch" : "min"
|
||||
localStorage.setItem("drawio-theme", newUi)
|
||||
setDrawioUi(newUi)
|
||||
setIsDrawioReady(false)
|
||||
resetDrawioReady()
|
||||
}
|
||||
|
||||
@@ -131,6 +139,7 @@ export default function Home() {
|
||||
newIsMobile !== isMobileRef.current
|
||||
) {
|
||||
saveDiagramToStorage().catch(() => {})
|
||||
setIsDrawioReady(false)
|
||||
resetDrawioReady()
|
||||
}
|
||||
isMobileRef.current = newIsMobile
|
||||
@@ -206,28 +215,35 @@ export default function Home() {
|
||||
mouseOverDrawioRef.current = false
|
||||
}}
|
||||
>
|
||||
<div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30">
|
||||
{isLoaded ? (
|
||||
<DrawIoEmbed
|
||||
key={`${drawioUi}-${darkMode}-${currentLang}`}
|
||||
ref={drawioRef}
|
||||
onExport={handleDiagramExport}
|
||||
onLoad={onDrawioLoad}
|
||||
onSave={handleDrawioSave}
|
||||
baseUrl={drawioBaseUrl}
|
||||
urlParameters={{
|
||||
ui: drawioUi,
|
||||
spin: true,
|
||||
libraries: false,
|
||||
saveAndExit: false,
|
||||
noExitBtn: true,
|
||||
dark: darkMode,
|
||||
lang: currentLang,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full flex items-center justify-center bg-background">
|
||||
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full" />
|
||||
<div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30 relative">
|
||||
{isLoaded && (
|
||||
<div
|
||||
className={`h-full w-full ${isDrawioReady ? "" : "invisible absolute inset-0"}`}
|
||||
>
|
||||
<DrawIoEmbed
|
||||
key={`${drawioUi}-${darkMode}-${currentLang}`}
|
||||
ref={drawioRef}
|
||||
onExport={handleDiagramExport}
|
||||
onLoad={handleDrawioLoad}
|
||||
onSave={handleDrawioSave}
|
||||
baseUrl={drawioBaseUrl}
|
||||
urlParameters={{
|
||||
ui: drawioUi,
|
||||
spin: false,
|
||||
libraries: false,
|
||||
saveAndExit: false,
|
||||
noExitBtn: true,
|
||||
dark: darkMode,
|
||||
lang: currentLang,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(!isLoaded || !isDrawioReady) && (
|
||||
<div className="h-full w-full bg-background flex items-center justify-center">
|
||||
<span className="text-muted-foreground">
|
||||
Draw.io panel is loading...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -193,6 +193,7 @@ interface ChatMessageDisplayProps {
|
||||
onRegenerate?: (messageIndex: number) => void
|
||||
onEditMessage?: (messageIndex: number, newText: string) => void
|
||||
status?: "streaming" | "submitted" | "idle" | "error" | "ready"
|
||||
isRestored?: boolean
|
||||
}
|
||||
|
||||
export function ChatMessageDisplay({
|
||||
@@ -205,6 +206,7 @@ export function ChatMessageDisplay({
|
||||
onRegenerate,
|
||||
onEditMessage,
|
||||
status = "idle",
|
||||
isRestored = false,
|
||||
}: ChatMessageDisplayProps) {
|
||||
const dict = useDictionary()
|
||||
const { chartXML, loadDiagram: onDisplayChart } = useDiagram()
|
||||
@@ -250,6 +252,15 @@ export function ChatMessageDisplay({
|
||||
const [expandedPdfSections, setExpandedPdfSections] = useState<
|
||||
Record<string, boolean>
|
||||
>({})
|
||||
// Track message IDs that were restored from localStorage (skip animation for these)
|
||||
const restoredMessageIdsRef = useRef<Set<string> | null>(null)
|
||||
|
||||
// Capture restored message IDs once when isRestored becomes true
|
||||
useEffect(() => {
|
||||
if (isRestored && restoredMessageIdsRef.current === null) {
|
||||
restoredMessageIdsRef.current = new Set(messages.map((m) => m.id))
|
||||
}
|
||||
}, [isRestored, messages])
|
||||
|
||||
const setCopyState = (
|
||||
messageId: string,
|
||||
@@ -669,7 +680,8 @@ export function ChatMessageDisplay({
|
||||
const renderToolPart = (part: ToolPartLike) => {
|
||||
const callId = part.toolCallId
|
||||
const { state, input, output } = part
|
||||
const isExpanded = expandedTools[callId] ?? true
|
||||
// Default to collapsed if tool is complete, expanded if still streaming
|
||||
const isExpanded = expandedTools[callId] ?? state !== "output-available"
|
||||
const toolName = part.type?.replace("tool-", "")
|
||||
const isCopied = copiedToolCallId === callId
|
||||
|
||||
@@ -859,9 +871,9 @@ export function ChatMessageDisplay({
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-full w-full scrollbar-thin">
|
||||
{messages.length === 0 ? (
|
||||
{messages.length === 0 && isRestored ? (
|
||||
<ExamplePanel setInput={setInput} setFiles={setFiles} />
|
||||
) : (
|
||||
) : messages.length === 0 ? null : (
|
||||
<div className="py-4 px-4 space-y-4">
|
||||
{messages.map((message, messageIndex) => {
|
||||
const userMessageText =
|
||||
@@ -881,13 +893,23 @@ export function ChatMessageDisplay({
|
||||
.slice(messageIndex + 1)
|
||||
.every((m) => m.role !== "user"))
|
||||
const isEditing = editingMessageId === message.id
|
||||
// Skip animation for restored messages
|
||||
// If isRestored but ref not set yet, we're in first render after restoration - treat all as restored
|
||||
const isRestoredMessage =
|
||||
isRestored &&
|
||||
(restoredMessageIdsRef.current === null ||
|
||||
restoredMessageIdsRef.current.has(message.id))
|
||||
return (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex w-full ${message.role === "user" ? "justify-end" : "justify-start"} animate-message-in`}
|
||||
style={{
|
||||
animationDelay: `${messageIndex * 50}ms`,
|
||||
}}
|
||||
className={`flex w-full ${message.role === "user" ? "justify-end" : "justify-start"} ${isRestoredMessage ? "" : "animate-message-in"}`}
|
||||
style={
|
||||
isRestoredMessage
|
||||
? undefined
|
||||
: {
|
||||
animationDelay: `${messageIndex * 50}ms`,
|
||||
}
|
||||
}
|
||||
>
|
||||
{message.role === "user" &&
|
||||
userMessageText &&
|
||||
@@ -984,6 +1006,9 @@ export function ChatMessageDisplay({
|
||||
isStreaming={
|
||||
isStreamingReasoning
|
||||
}
|
||||
defaultOpen={
|
||||
!isRestoredMessage
|
||||
}
|
||||
>
|
||||
<ReasoningTrigger />
|
||||
<ReasoningContent>
|
||||
|
||||
@@ -201,6 +201,7 @@ export default function ChatPanel({
|
||||
|
||||
// Flag to track if we've restored from localStorage
|
||||
const hasRestoredRef = useRef(false)
|
||||
const [isRestored, setIsRestored] = useState(false)
|
||||
|
||||
// Ref to track latest chartXML for use in callbacks (avoids stale closure)
|
||||
const chartXMLRef = useRef(chartXML)
|
||||
@@ -457,6 +458,8 @@ export default function ChatPanel({
|
||||
localStorage.removeItem(STORAGE_MESSAGES_KEY)
|
||||
localStorage.removeItem(STORAGE_XML_SNAPSHOTS_KEY)
|
||||
toast.error(dict.errors.sessionCorrupted)
|
||||
} finally {
|
||||
setIsRestored(true)
|
||||
}
|
||||
}, [setMessages])
|
||||
|
||||
@@ -1006,6 +1009,7 @@ export default function ChatPanel({
|
||||
onRegenerate={handleRegenerate}
|
||||
status={status}
|
||||
onEditMessage={handleEditMessage}
|
||||
isRestored={isRestored}
|
||||
/>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const DRAWIO_ORIGIN = getOrigin(DRAWIO_BASE_URL)
|
||||
// Normalize URL for iframe src - ensure no double slashes
|
||||
function normalizeUrl(url: string): string {
|
||||
// Remove trailing slash to avoid double slashes
|
||||
return url.replace(/\/$/, '')
|
||||
return url.replace(/\/$/, "")
|
||||
}
|
||||
|
||||
interface SessionState {
|
||||
|
||||
Reference in New Issue
Block a user