feat: implement Undo/Redo/Timeline and Fork Features and fixed opencode.json reading issue on broken json (#99)
* feat: add /undo, /redo, /timeline slash commands and fork button
- Add /undo command to revert to previous user message
- Add /redo command to redo previously undone messages
- Add /timeline command to show conversation history
- Add fork button on user messages (hover) to create new session
- Add TimelineDialog component for navigating conversation history
- Show undo/redo/timeline in autocomplete when session exists
- Fix fork to use SDK session.fork API
- Silent no-op behavior for undo/redo when no messages (matches OpenCode CLI)
* fix: correct fork session endpoint and session switching
- Add hybrid SDK approach using v1 for existing methods and v2 for fork only
- v2 SDK has correct endpoint /session/{sessionID}/fork instead of broken /session/{id}/fork
- Fix forkFromMessage to use setCurrentSession instead of direct set
- Ensures both useSessionStore and useSessionManagementStore are consistent
- Fixes issue where forked sessions appeared empty until sending a new message
* fix: use direct fetch for fork to avoid v2 SDK build errors
- Remove v2 SDK imports and hybrid SDK approach
- Use direct fetch() call to /api/session/{sessionID}/fork endpoint
- Avoids type conflicts between v1 and v2 SDK
- Bypasses broken v1 SDK fork endpoint (wrong path parameter)
- All codebase now uses v1 SDK consistently
* fix: use jsonc-parser for config file parsing
- Replace strip-json-comments + JSON.parse with jsonc-parser
- Handles comments, trailing commas, and unquoted keys
- Matches OpenCode CLI config parsing behavior
- Fixes SyntaxError when reading global opencode.json config
* feat: improve timeline dialog with loading state and search
- Add loading spinner on fork button during operation (~30s on big sessions)
- Refresh session list after fork so new session appears in sidebar immediately
- Add search bar to filter messages by prompt text content
- Show 'No messages found' when search has no results
- Disable fork button while forking to prevent duplicate operations
This commit is contained in:
@@ -82,7 +82,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
|
||||
|
||||
const { currentProviderId, currentModelId, currentAgentName, setAgent, getVisibleAgents } = useConfigStore();
|
||||
const agents = getVisibleAgents();
|
||||
const { isMobile, inputBarOffset, isKeyboardOpen } = useUIStore();
|
||||
const { isMobile, inputBarOffset, isKeyboardOpen, setTimelineDialogOpen } = useUIStore();
|
||||
const { working } = useAssistantStatus();
|
||||
const [showAbortStatus, setShowAbortStatus] = React.useState(false);
|
||||
const abortTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -355,7 +355,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
|
||||
textareaRef.current?.blur();
|
||||
}
|
||||
|
||||
// Handle /summarize command scroll
|
||||
// Handle slash commands locally before sending
|
||||
const normalizedCommand = primaryText.trimStart();
|
||||
if (normalizedCommand.startsWith('/')) {
|
||||
const commandName = normalizedCommand
|
||||
@@ -363,7 +363,29 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
|
||||
.trim()
|
||||
.split(/\s+/)[0]
|
||||
?.toLowerCase();
|
||||
if (commandName === 'summarize') {
|
||||
|
||||
// NEW: /undo - revert to last message (populates input with reverted message text)
|
||||
if (commandName === 'undo' && currentSessionId) {
|
||||
await useSessionStore.getState().handleSlashUndo(currentSessionId);
|
||||
// Don't clear message - pendingInputText will populate it with reverted message
|
||||
scrollToBottom?.({ instant: true, force: true });
|
||||
return; // Don't send to assistant
|
||||
}
|
||||
// NEW: /redo - unrevert or partial redo (populates input with message text)
|
||||
else if (commandName === 'redo' && currentSessionId) {
|
||||
await useSessionStore.getState().handleSlashRedo(currentSessionId);
|
||||
// Don't clear message - pendingInputText will populate it
|
||||
scrollToBottom?.({ instant: true, force: true });
|
||||
return; // Don't send to assistant
|
||||
}
|
||||
// NEW: /timeline - open timeline dialog
|
||||
else if (commandName === 'timeline' && currentSessionId) {
|
||||
setTimelineDialogOpen(true);
|
||||
setMessage('');
|
||||
return; // Don't send to assistant
|
||||
}
|
||||
// Existing: /summarize command scroll
|
||||
else if (commandName === 'summarize') {
|
||||
scrollToBottom?.({ instant: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user