diff options
| author | blackhao <13851610112@163.com> | 2025-12-14 01:53:35 -0600 |
|---|---|---|
| committer | blackhao <13851610112@163.com> | 2025-12-14 01:53:35 -0600 |
| commit | 41a08c6f1818d991108725bf671b8934e5cc13bf (patch) | |
| tree | 386367a826ec98a5d8256b24bbdb48370f21be7e /frontend | |
| parent | 2956d4b67ca24991e0962f2be7f849ec5b682be6 (diff) | |
auto save
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/src/components/LeftSidebar.tsx | 42 | ||||
| -rw-r--r-- | frontend/src/store/flowStore.ts | 51 |
2 files changed, 89 insertions, 4 deletions
diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx index 8f9f202..6806ca3 100644 --- a/frontend/src/components/LeftSidebar.tsx +++ b/frontend/src/components/LeftSidebar.tsx @@ -36,7 +36,9 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { renameProjectItem, deleteProjectItem, setCurrentBlueprintPath, - clearBlueprint + clearBlueprint, + autoSave, + setAutoSave } = useFlowStore(); const { user, logout } = useAuthStore(); const { setViewport, getViewport } = useReactFlow(); @@ -66,6 +68,14 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { const [keysMessage, setKeysMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const { getAuthHeader } = useAuthStore(); + // Load auto-save preference from localStorage on mount + useEffect(() => { + const saved = localStorage.getItem('contextflow-autosave'); + if (saved === 'true') { + setAutoSave(true); + } + }, [setAutoSave]); + // Load API keys when settings modal opens useEffect(() => { if (showUserSettings) { @@ -668,7 +678,7 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { <div> <p className={`font-medium mb-1 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>🎯 Basic Operations</p> <ul className="list-disc list-inside space-y-1 ml-1"> - <li><span className="font-medium">Double-click</span> canvas to create a node</li> + <li><span className="font-medium">Right-click</span> canvas to create a node</li> <li><span className="font-medium">Drag</span> from handle to connect nodes</li> <li><span className="font-medium">Click</span> node to edit in right sidebar</li> <li><span className="font-medium">Delete</span> key to remove selected node</li> @@ -705,11 +715,37 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { <div className={`p-2 rounded ${isDark ? 'bg-blue-900/30' : 'bg-blue-50'}`}> <p className={`font-medium ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>💡 Tip</p> - <p>Use <span className="font-medium">Ctrl+S</span> to save, or enable auto-save in blueprints.</p> + <p>Use <span className="font-medium">Ctrl+S</span> to save, or enable auto-save below.</p> </div> </div> )} </div> + + {/* Auto-Save Toggle */} + <div className={`border-t pt-4 ${isDark ? 'border-gray-700' : 'border-gray-200'}`}> + <div className="flex items-center justify-between"> + <span className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}> + Auto-Save + </span> + <button + onClick={() => setAutoSave(!autoSave)} + className={`relative w-11 h-6 rounded-full transition-colors ${ + autoSave + ? 'bg-blue-600' + : isDark ? 'bg-gray-600' : 'bg-gray-300' + }`} + > + <span + className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${ + autoSave ? 'translate-x-5' : 'translate-x-0' + }`} + /> + </button> + </div> + <p className={`text-xs mt-1 ${isDark ? 'text-gray-500' : 'text-gray-400'}`}> + Automatically save blueprint after changes (2s delay) + </p> + </div> </div> {/* Footer */} diff --git a/frontend/src/store/flowStore.ts b/frontend/src/store/flowStore.ts index a919498..e2cd5d1 100644 --- a/frontend/src/store/flowStore.ts +++ b/frontend/src/store/flowStore.ts @@ -151,6 +151,7 @@ interface FlowState { currentBlueprintPath?: string; lastViewport?: ViewportState; saveStatus: SaveStatus; + autoSave: boolean; onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; @@ -205,6 +206,8 @@ interface FlowState { setCurrentBlueprintPath: (path?: string) => void; setLastViewport: (viewport: ViewportState) => void; saveCurrentBlueprint: (path?: string, viewport?: ViewportState) => Promise<void>; + setAutoSave: (enabled: boolean) => void; + triggerAutoSave: () => void; clearBlueprint: () => void; loadArchivedNodes: () => Promise<void>; saveArchivedNodes: () => Promise<void>; @@ -305,6 +308,7 @@ const useFlowStore = create<FlowState>((set, get) => { currentBlueprintPath: undefined, lastViewport: undefined, saveStatus: 'idle', + autoSave: false, toggleTheme: () => { const newTheme = get().theme === 'light' ? 'dark' : 'light'; @@ -430,8 +434,9 @@ const useFlowStore = create<FlowState>((set, get) => { }, onNodesChange: (changes: NodeChange[]) => { - // Check if any nodes are being removed + // Check if any nodes are being removed or positions are changing const hasRemovals = changes.some(c => c.type === 'remove'); + const hasPositionChanges = changes.some(c => c.type === 'position' && c.dragging === false); set({ nodes: applyNodeChanges(changes, get().nodes) as LLMNode[], @@ -445,12 +450,24 @@ const useFlowStore = create<FlowState>((set, get) => { }); get().propagateTraces(); } + + // Trigger auto-save for removals or position changes (after drag ends) + if (hasRemovals || hasPositionChanges) { + get().triggerAutoSave(); + } }, onEdgesChange: (changes: EdgeChange[]) => { + const hasRemovals = changes.some(c => c.type === 'remove'); + set({ edges: applyEdgeChanges(changes, get().edges), }); get().propagateTraces(); + + // Trigger auto-save for edge changes + if (hasRemovals) { + get().triggerAutoSave(); + } }, onConnect: (connection: Connection) => { const { nodes, edges } = get(); @@ -901,11 +918,13 @@ const useFlowStore = create<FlowState>((set, get) => { }, get().edges), }); setTimeout(() => get().propagateTraces(), 0); + get().triggerAutoSave(); }, addNode: (node: LLMNode) => { set((state) => ({ nodes: [...state.nodes, node] })); setTimeout(() => get().propagateTraces(), 0); + get().triggerAutoSave(); }, updateNodeData: (nodeId: string, data: Partial<NodeData>) => { @@ -923,6 +942,11 @@ const useFlowStore = create<FlowState>((set, get) => { if (data.response !== undefined) { get().propagateTraces(); } + + // Trigger auto-save for important changes (not just userPrompt typing) + if (data.response !== undefined || data.status !== undefined || data.attachedFileIds !== undefined) { + get().triggerAutoSave(); + } }, setSelectedNode: (nodeId: string | null) => { @@ -1565,6 +1589,31 @@ const useFlowStore = create<FlowState>((set, get) => { } }, + setAutoSave: (enabled: boolean) => { + set({ autoSave: enabled }); + // Save preference to localStorage + localStorage.setItem('contextflow-autosave', enabled ? 'true' : 'false'); + }, + + triggerAutoSave: (() => { + let timeoutId: ReturnType<typeof setTimeout> | null = null; + return () => { + const state = useFlowStore.getState(); + if (!state.autoSave || !state.currentBlueprintPath) return; + + // Debounce: wait 2 seconds after last change before saving + if (timeoutId) clearTimeout(timeoutId); + timeoutId = setTimeout(async () => { + try { + await state.saveCurrentBlueprint(); + console.log('[AutoSave] Blueprint saved'); + } catch (e) { + console.error('[AutoSave] Failed to save:', e); + } + }, 2000); + }; + })(), + clearBlueprint: () => { set({ nodes: [], |
