mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-03 19:15:30 +08:00
### What problem does this PR solve? feat(agent): Added history management and paste handling features #3221 - Added a PasteHandlerPlugin to handle paste operations, optimizing the multi-line text pasting experience - Implemented the AgentHistoryManager class to manage history, supporting undo and redo functionality - Integrates history management functionality into the Agent component ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
163
web/src/pages/agent/use-agent-history-manager.ts
Normal file
163
web/src/pages/agent/use-agent-history-manager.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import useGraphStore from './store';
|
||||
|
||||
// History management class
|
||||
export class HistoryManager {
|
||||
private history: { nodes: any[]; edges: any[] }[] = [];
|
||||
private currentIndex: number = -1;
|
||||
private readonly maxSize: number = 50; // Limit maximum number of history records
|
||||
private setNodes: (nodes: any[]) => void;
|
||||
private setEdges: (edges: any[]) => void;
|
||||
private lastSavedState: string = ''; // Used to compare if state has changed
|
||||
|
||||
constructor(
|
||||
setNodes: (nodes: any[]) => void,
|
||||
setEdges: (edges: any[]) => void,
|
||||
) {
|
||||
this.setNodes = setNodes;
|
||||
this.setEdges = setEdges;
|
||||
}
|
||||
|
||||
// Compare if two states are equal
|
||||
private statesEqual(
|
||||
state1: { nodes: any[]; edges: any[] },
|
||||
state2: { nodes: any[]; edges: any[] },
|
||||
): boolean {
|
||||
return JSON.stringify(state1) === JSON.stringify(state2);
|
||||
}
|
||||
|
||||
push(nodes: any[], edges: any[]) {
|
||||
const currentState = {
|
||||
nodes: JSON.parse(JSON.stringify(nodes)),
|
||||
edges: JSON.parse(JSON.stringify(edges)),
|
||||
};
|
||||
|
||||
// If state hasn't changed, don't save
|
||||
if (
|
||||
this.history.length > 0 &&
|
||||
this.statesEqual(currentState, this.history[this.currentIndex])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If current index is not at the end of history, remove subsequent states
|
||||
if (this.currentIndex < this.history.length - 1) {
|
||||
this.history.splice(this.currentIndex + 1);
|
||||
}
|
||||
|
||||
// Add current state
|
||||
this.history.push(currentState);
|
||||
|
||||
// Limit history record size
|
||||
if (this.history.length > this.maxSize) {
|
||||
this.history.shift();
|
||||
this.currentIndex = this.history.length - 1;
|
||||
} else {
|
||||
this.currentIndex = this.history.length - 1;
|
||||
}
|
||||
|
||||
// Update last saved state
|
||||
this.lastSavedState = JSON.stringify(currentState);
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.canUndo()) {
|
||||
this.currentIndex--;
|
||||
const prevState = this.history[this.currentIndex];
|
||||
this.setNodes(JSON.parse(JSON.stringify(prevState.nodes)));
|
||||
this.setEdges(JSON.parse(JSON.stringify(prevState.edges)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
redo() {
|
||||
console.log('redo');
|
||||
if (this.canRedo()) {
|
||||
this.currentIndex++;
|
||||
const nextState = this.history[this.currentIndex];
|
||||
this.setNodes(JSON.parse(JSON.stringify(nextState.nodes)));
|
||||
this.setEdges(JSON.parse(JSON.stringify(nextState.edges)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canUndo() {
|
||||
return this.currentIndex > 0;
|
||||
}
|
||||
|
||||
canRedo() {
|
||||
return this.currentIndex < this.history.length - 1;
|
||||
}
|
||||
|
||||
// Reset history records
|
||||
reset() {
|
||||
this.history = [];
|
||||
this.currentIndex = -1;
|
||||
this.lastSavedState = '';
|
||||
}
|
||||
}
|
||||
|
||||
export const useAgentHistoryManager = () => {
|
||||
// Get current state and history state
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const setNodes = useGraphStore((state) => state.setNodes);
|
||||
const setEdges = useGraphStore((state) => state.setEdges);
|
||||
|
||||
// Use useRef to keep HistoryManager instance unchanged
|
||||
const historyManagerRef = useRef<HistoryManager | null>(null);
|
||||
|
||||
// Initialize HistoryManager
|
||||
if (!historyManagerRef.current) {
|
||||
historyManagerRef.current = new HistoryManager(setNodes, setEdges);
|
||||
}
|
||||
|
||||
const historyManager = historyManagerRef.current;
|
||||
|
||||
// Save state history - use useEffect instead of useMemo to avoid re-rendering
|
||||
useEffect(() => {
|
||||
historyManager.push(nodes, edges);
|
||||
}, [nodes, edges, historyManager]);
|
||||
|
||||
// Keyboard event handling
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Check if focused on an input element
|
||||
const activeElement = document.activeElement;
|
||||
const isInputFocused =
|
||||
activeElement instanceof HTMLInputElement ||
|
||||
activeElement instanceof HTMLTextAreaElement ||
|
||||
activeElement?.hasAttribute('contenteditable');
|
||||
|
||||
// Skip keyboard shortcuts if typing in an input field
|
||||
if (isInputFocused) {
|
||||
return;
|
||||
}
|
||||
// Ctrl+Z or Cmd+Z undo
|
||||
if (
|
||||
(e.ctrlKey || e.metaKey) &&
|
||||
(e.key === 'z' || e.key === 'Z') &&
|
||||
!e.shiftKey
|
||||
) {
|
||||
e.preventDefault();
|
||||
historyManager.undo();
|
||||
}
|
||||
// Ctrl+Shift+Z or Cmd+Shift+Z redo
|
||||
else if (
|
||||
(e.ctrlKey || e.metaKey) &&
|
||||
(e.key === 'z' || e.key === 'Z') &&
|
||||
e.shiftKey
|
||||
) {
|
||||
e.preventDefault();
|
||||
historyManager.redo();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [historyManager]);
|
||||
};
|
||||
Reference in New Issue
Block a user