Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to

### Added

- AI assistant diff blocks show the step with its adaptor logo. Clicking it
opens the step and highlights the code the AI added.
- GitHub sync connections using the v2 format now install `@v2` of the OpenFn
deploy and pull GitHub Actions, which sync `openfn.yaml` workspaces with
`openfn project pull/deploy`. Lightning no longer commits a config file for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { randomUUID } from '#/common';
import { useURLState } from '../../react/lib/use-url-state';
import {
useMonacoRef,
usePendingHighlight,
useRegisterDiffDismissalCallback,
} from '../contexts/MonacoRefContext';
import {
Expand Down Expand Up @@ -68,6 +69,7 @@ import {
useWorkflowActions,
useWorkflowReadOnly,
useWorkflowState,
useWorkflowStoreContext,
} from '../hooks/useWorkflow';
import { useKeyboardShortcut } from '../keyboard';
import { usePinnedView } from '../lib/pinnedView';
Expand All @@ -82,6 +84,7 @@ import {
import { AIAssistantPanel } from './AIAssistantPanel';
import { AlertDialog } from './AlertDialog';
import { MessageList } from './MessageList';
import type { OpenStepTarget } from './WorkflowDiffBlocks';

/**
* AIAssistantPanelWrapper Component
Expand Down Expand Up @@ -208,12 +211,16 @@ export function AIAssistantPanelWrapper({
[jobs]
);

const workflowStore = useWorkflowStoreContext();
const { setPendingHighlight } = usePendingHighlight();
const handleOpenStep = useCallback(
({ jobId, name }: { jobId?: string; name: string }) => {
({ jobId, name, added }: OpenStepTarget) => {
const match =
(jobId && jobs.find(job => job.id === jobId)) ??
jobs.find(job => job.name === name);
if (!match) return;
const ytext = workflowStore.getJobBodyYText(match.id);
if (ytext) setPendingHighlight?.({ ytext, added });
// Clears the sibling selections too: leaving a stale trigger or edge
// behind still resolves to the job, but leaves a URL that is wrong to
// share and wrong to go back to.
Expand All @@ -224,7 +231,7 @@ export function AIAssistantPanelWrapper({
edge: null,
});
},
[jobs, updateSearchParams]
[jobs, updateSearchParams, workflowStore, setPendingHighlight]
);
const edges = useWorkflowState(state => state.edges);
const positions = useWorkflowState(state => state.positions);
Expand Down
3 changes: 2 additions & 1 deletion assets/js/collaborative-editor/components/AdaptorIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { extractAdaptorName, extractPackageName } from '../utils/adaptorUtils';

interface AdaptorIconProps {
name: string;
size?: 'sm' | 'md' | 'lg';
size?: 'xs' | 'sm' | 'md' | 'lg';
}

const sizeClasses = {
xs: 'h-3.5 w-3.5',
sm: 'h-5 w-5',
md: 'h-8 w-8',
lg: 'h-12 w-12',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ import _logger from '#/utils/logger';

import { type Monaco, MonacoEditor, setTheme } from '../../monaco';
import { addKeyboardShortcutOverrides } from '../../monaco/keyboard-overrides';
import { useHandleDiffDismissed } from '../contexts/MonacoRefContext';
import {
useHandleDiffDismissed,
usePendingHighlight,
} from '../contexts/MonacoRefContext';

import { locateAddedRuns } from '../utils/locateAddedRuns';
import createCompletionProvider from '../utils/magic-completion';

import { LoadingIndicator } from './common/LoadingIndicator';
Expand Down Expand Up @@ -174,6 +178,55 @@ export const CollaborativeMonaco = forwardRef<
};
}, []);

const highlightRef = useRef<editor.IEditorDecorationsCollection>();
// Monaco keeps a decoration's DOM when its markup is unchanged, so a
// repeat highlight alternates class names to restart the flash.
const replayRef = useRef(false);
const { pendingHighlight, setPendingHighlight } = usePendingHighlight();

// Runs after the binding effect, so the model already holds this ytext.
useEffect(() => {
const editor = editorRef.current;
const model = editor?.getModel();
if (!editor || !model) return;
if (!pendingHighlight || pendingHighlight.ytext !== ytext) return;
setPendingHighlight?.(null);

const ranges = locateAddedRuns(model, pendingHighlight.added);

const className = replayRef.current
? 'ai-change-highlight ai-change-replay'
: 'ai-change-highlight';
replayRef.current = !replayRef.current;
highlightRef.current?.clear();
highlightRef.current = editor.createDecorationsCollection(
ranges.map(range => ({
range,
options: { className },
}))
);
// A run edited since can't be found, so scroll to where it was rather
// than leave the click looking like it did nothing.
const fallbackLine = pendingHighlight.added[0]?.line;
if (ranges[0]) editor.revealRangeInCenterIfOutsideViewport(ranges[0]);
else if (fallbackLine)
editor.revealLineInCenterIfOutsideViewport(
Math.min(fallbackLine, model.getLineCount())
);
}, [pendingHighlight, ytext, editorReady, setPendingHighlight]);

// Dismissed the way a selection is: by clicking or typing in the code.
useEffect(() => {
const editor = editorRef.current;
if (!editor) return;
const clear = () => highlightRef.current?.clear();
const subscriptions = [editor.onMouseDown(clear), editor.onKeyDown(clear)];
return () => {
clear();
subscriptions.forEach(subscription => subscription.dispose());
};
}, [ytext, editorReady]);

useEffect(() => {
if (editorRef.current) {
editorRef.current.updateOptions({ readOnly: disabled });
Expand Down
7 changes: 4 additions & 3 deletions assets/js/collaborative-editor/components/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
deriveWorkflowChanges,
} from '../utils/workflowDiff';

import type { OpenStepTarget } from './WorkflowDiffBlocks';
import {
StepDiffBlock,
StructureBlock,
Expand Down Expand Up @@ -246,7 +247,7 @@ const SegmentTimeline = ({
streaming?: boolean;
showAddButtons?: boolean;
isWriteDisabled?: boolean;
onOpenStep?: (step: { jobId?: string; name: string }) => void;
onOpenStep?: (step: OpenStepTarget) => void;
canOpenStep?: (step: { jobId?: string; name: string }) => boolean;
/**
* Per-status change sets (segment index → what that action changed),
Expand Down Expand Up @@ -356,7 +357,7 @@ const WorkflowReplyTimeline = ({
streaming?: boolean;
showAddButtons?: boolean;
isWriteDisabled?: boolean;
onOpenStep?: (step: { jobId?: string; name: string }) => void;
onOpenStep?: (step: OpenStepTarget) => void;
canOpenStep?: (step: { jobId?: string; name: string }) => boolean;
}) => {
// The snapshot path only needs to know how many segments have drained,
Expand Down Expand Up @@ -752,7 +753,7 @@ interface MessageListProps {
/** Id of the viewer, so a collaborator's message is not mistaken for theirs */
currentUserId?: string;
/** Opens a step in the IDE from a diff block */
onOpenStep?: (step: { jobId?: string; name: string }) => void;
onOpenStep?: (step: OpenStepTarget) => void;
canOpenStep?: (step: { jobId?: string; name: string }) => boolean;
/**
* Global replies whose auto-apply failed. They get the code panel and its
Expand Down
Loading
Loading