Skip to content

Commit

Permalink
refactor(workflow)!: unify node traversal and improve protocol naming
Browse files Browse the repository at this point in the history
- Rename WorkflowFromExtension interface to ExtensionToWorkflow for clarity
- Implement recursive traversal for pre-loop node detection
- Add unified node connection helpers and markdown rendering support
- Enhance type safety in components and message handlers
- Optimize edge filtering and graph traversal patterns

BREAKING CHANGE: WorkflowFromExtension interface renamed to ExtensionToWorkflow
  • Loading branch information
PriNova committed Jan 25, 2025
1 parent ce6acf2 commit f22f71f
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 198 deletions.
267 changes: 155 additions & 112 deletions vscode/src/workflow/node-sorting.ts

Large diffs are not rendered by default.

36 changes: 19 additions & 17 deletions vscode/src/workflow/workflow-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as vscode from 'vscode'
import type { Edge } from '../../webviews/workflow/components/CustomOrderedEdge'
import { getInactiveNodes } from '../../webviews/workflow/components/hooks/nodeStateTransforming'
import { NodeType, type WorkflowNodes } from '../../webviews/workflow/components/nodes/Nodes'
import type { WorkflowFromExtension } from '../../webviews/workflow/services/WorkflowProtocol'
import type { ExtensionToWorkflow } from '../../webviews/workflow/services/WorkflowProtocol'
import { ChatController, type ChatSession } from '../chat/chat-view/ChatController'
import { type ContextRetriever, toStructuredMentions } from '../chat/chat-view/ContextRetriever'
import { getCorpusContextItemsForEditorState } from '../chat/initialContext'
Expand Down Expand Up @@ -93,7 +93,7 @@ export async function executeWorkflow(

await webview.postMessage({
type: 'execution_started',
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

for (const node of sortedNodes) {
if (node.type === NodeType.LOOP_START) {
Expand All @@ -112,7 +112,7 @@ export async function executeWorkflow(
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status: 'running' },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

let result: string | string[]
try {
Expand Down Expand Up @@ -145,11 +145,11 @@ export async function executeWorkflow(
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status, result: errorMessage },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

await webview.postMessage({
type: 'execution_completed',
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
return
}
break
Expand Down Expand Up @@ -233,11 +233,11 @@ export async function executeWorkflow(
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status, result: errorMessage },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

await webview.postMessage({
type: 'execution_completed',
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

return
}
Expand Down Expand Up @@ -278,7 +278,7 @@ export async function executeWorkflow(
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status: 'interrupted' },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
return
}
const errorMessage = error instanceof Error ? error.message : String(error)
Expand All @@ -287,25 +287,25 @@ export async function executeWorkflow(
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status, result: errorMessage },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

await webview.postMessage({
type: 'execution_completed',
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
return
}

context.nodeOutputs.set(node.id, result)
await webview.postMessage({
type: 'node_execution_status',
data: { nodeId: node.id, status: 'completed', result },
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
}

persistentShell.dispose()
webview.postMessage({
type: 'execution_completed',
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

context.loopStates.clear()
}
Expand Down Expand Up @@ -449,7 +449,7 @@ export async function executeCLINode(
status: 'pending_approval',
result: `${filteredCommand}`,
},
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

const approval = await approvalHandler(node.id)
if (approval.command) {
Expand Down Expand Up @@ -596,7 +596,7 @@ async function executePreviewNode(
nodeId,
count: tokenCount.length,
},
} as WorkflowFromExtension)
} as ExtensionToWorkflow)

return trimmedInput
}
Expand Down Expand Up @@ -661,17 +661,19 @@ async function executeSearchContextNode(
* Executes an output node in the workflow, which involves continuing a chat session and waiting for new messages.
*
* @param input - The input string to be used for the chat.
* @param contextItemsAsString - An optional array of strings representing the context items to be used for the chat.
* @param contextItemsAsStringArray - An optional array of strings representing the context items to be used for the chat.
* @param abortSignal - An AbortSignal that can be used to cancel the workflow execution.
* @returns A Promise that resolves with the session ID of the completed chat session.
*/
async function executeCodyOutputNode(
input: string,
contextItemsAsString: string[] | undefined,
contextItemsAsStringArray: string[] | undefined,
abortSignal: AbortSignal
): Promise<string> {
abortSignal.throwIfAborted()
const stringToContext = contextItemsAsString ? stringToContextItems(contextItemsAsString) : []
const stringToContext = contextItemsAsStringArray
? stringToContextItems(contextItemsAsStringArray)
: []

return new Promise<string>((resolve, reject) => {
// Handle workflow abortion
Expand Down
13 changes: 9 additions & 4 deletions vscode/src/workflow/workflow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from 'vscode'
import type {
WorkflowFromExtension,
ExtensionToWorkflow,
WorkflowToExtension,
} from '../../webviews/workflow/services/WorkflowProtocol'

Expand Down Expand Up @@ -63,7 +63,7 @@ export function registerWorkflowCommands(
panel.webview.postMessage({
type: 'models_loaded',
data: models,
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
chatModelsSubscription.unsubscribe()
}
})
Expand All @@ -78,7 +78,7 @@ export function registerWorkflowCommands(
panel.webview.postMessage({
type: 'workflow_loaded',
data: loadedData,
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
}
break
}
Expand Down Expand Up @@ -115,7 +115,7 @@ export function registerWorkflowCommands(
nodeId: message.data.nodeId,
count: count.length,
},
} as WorkflowFromExtension)
} as ExtensionToWorkflow)
break
}
case 'node_approved': {
Expand All @@ -127,6 +127,11 @@ export function registerWorkflowCommands(
}
break
}
case 'open_external_link': {
const url = vscode.Uri.parse(message.url)
vscode.env.openExternal(url)
break
}
}
},
undefined,
Expand Down
4 changes: 2 additions & 2 deletions vscode/webviews/workflow/WorkflowApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type React from 'react'
import '../index.css'
import type { GenericVSCodeWrapper } from '@sourcegraph/cody-shared'
import { Flow } from './components/Flow'
import type { WorkflowFromExtension, WorkflowToExtension } from './services/WorkflowProtocol'
import type { ExtensionToWorkflow, WorkflowToExtension } from './services/WorkflowProtocol'

export const WorkflowApp: React.FC<{
vscodeAPI: GenericVSCodeWrapper<WorkflowToExtension, WorkflowFromExtension>
vscodeAPI: GenericVSCodeWrapper<WorkflowToExtension, ExtensionToWorkflow>
}> = vscodeAPI => {
return (
<ReactFlowProvider>
Expand Down
33 changes: 22 additions & 11 deletions vscode/webviews/workflow/components/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Background, Controls, ReactFlow } from '@xyflow/react'
import '@xyflow/react/dist/style.css'
import type { GenericVSCodeWrapper, Model } from '@sourcegraph/cody-shared'
import type React from 'react'
import { useMemo, useState } from 'react'
import type { WorkflowFromExtension, WorkflowToExtension } from '../services/WorkflowProtocol'
import { useCallback, useMemo, useState } from 'react'
import type { ExtensionToWorkflow, WorkflowToExtension } from '../services/WorkflowProtocol'
import { CustomOrderedEdge } from './CustomOrderedEdge'
import styles from './Flow.module.css'
import { HelpModal } from './HelpModal'
Expand All @@ -25,11 +25,11 @@ import { type WorkflowNodes, defaultWorkflow, nodeTypes } from './nodes/Nodes'
*
* @component
* @param {Object} props - Component properties
* @param {GenericVSCodeWrapper<WorkflowToExtension, WorkflowFromExtension>} props.vscodeAPI - VSCode API wrapper for communication between webview and extension
* @param {GenericVSCodeWrapper<WorkflowToExtension, ExtensionToWorkflow>} props.vscodeAPI - VSCode API wrapper for communication between webview and extension
* @returns {React.ReactElement} Rendered workflow interface with ReactFlow, sidebars, and controls
*/
export const Flow: React.FC<{
vscodeAPI: GenericVSCodeWrapper<WorkflowToExtension, WorkflowFromExtension>
vscodeAPI: GenericVSCodeWrapper<WorkflowToExtension, ExtensionToWorkflow>
}> = ({ vscodeAPI }) => {
// Node-related state
const [nodes, setNodes] = useState<WorkflowNodes[]>(defaultWorkflow.nodes)
Expand Down Expand Up @@ -100,6 +100,13 @@ export const Flow: React.FC<{
const { onNodeClick, handleBackgroundClick, handleBackgroundKeyDown } =
useInteractionHandling(setSelectedNode)

const handlePostMessage = useCallback(
(message: WorkflowToExtension) => {
vscodeAPI.postMessage(message)
},
[vscodeAPI]
)

const nodesWithState = useNodeStateTransformation(
nodes,
selectedNode,
Expand All @@ -109,12 +116,16 @@ export const Flow: React.FC<{
nodeResults,
interruptedNodeId,
edges
).map(node => ({
...node,
data: {
...node.data,
},
}))
).map(node => {
const finalNode = {
...node,
data: {
...node.data,
handlePostMessage: handlePostMessage,
},
}
return finalNode
})

// Recalculate sortedNodes on each render
const sortedNodes = useMemo(() => {
Expand All @@ -125,7 +136,6 @@ export const Flow: React.FC<{
data: { ...node.data }, // Ensure data object is properly cloned
}))
}, [nodesWithState, edges])

return (
<div className="tw-flex tw-h-screen tw-w-full tw-border-2 tw-border-solid tw-border-[var(--vscode-panel-border)] tw-text-[14px] tw-overflow-hidden">
<div
Expand Down Expand Up @@ -197,6 +207,7 @@ export const Flow: React.FC<{
className="tw-flex-shrink-0 tw-border-r tw-border-solid tw-border-[var(--vscode-panel-border)] tw-bg-[var(--vscode-sideBar-background)] tw-h-full tw-overflow-y-auto"
>
<RightSidebar
handlePostMessage={handlePostMessage}
sortedNodes={sortedNodes}
nodeResults={nodeResults}
executingNodeId={executingNodeId}
Expand Down
51 changes: 39 additions & 12 deletions vscode/webviews/workflow/components/RightSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
} from '../../components/shadcn/ui/accordion'
import { Button } from '../../components/shadcn/ui/button'
import { Textarea } from '../../components/shadcn/ui/textarea'
import type { WorkflowToExtension } from '../services/WorkflowProtocol'
import { SimpleMarkdown } from './SimpleMarkdown'
import { NodeType, type WorkflowNodes } from './nodes/Nodes'

interface RightSidebarProps {
handlePostMessage: (message: WorkflowToExtension) => void
sortedNodes: WorkflowNodes[]
nodeResults: Map<string, string>
executingNodeId: string | null
Expand All @@ -20,6 +23,7 @@ interface RightSidebarProps {
}

export const RightSidebar: React.FC<RightSidebarProps> = ({
handlePostMessage,
sortedNodes,
nodeResults,
executingNodeId,
Expand Down Expand Up @@ -66,7 +70,12 @@ export const RightSidebar: React.FC<RightSidebarProps> = ({
}, [pendingApprovalNodeId])

return (
<div className="tw-w-full tw-border-r tw-border-border tw-h-full tw-bg-sidebar-background tw-p-4">
<div
className="tw-w-full tw-border-r tw-border-border tw-h-full tw-bg-sidebar-background tw-p-4"
style={{ paddingBottom: '20px' }}
>
{' '}
{/* Add paddingBottom here */}
<div className="tw-flex tw-flex-col tw-gap-2 tw-mb-4">
<h3 className="tw-text-[var(--vscode-sideBarTitle-foreground)] tw-font-medium tw-mb-4">
Workflow Execution Order & Results
Expand All @@ -92,17 +101,35 @@ export const RightSidebar: React.FC<RightSidebarProps> = ({
<AccordionContent>
{nodeResults.has(node.id) && (
<div className="tw-mt-1">
<Textarea
value={
modifiedCommands.get(node.id) ||
nodeResults.get(node.id) ||
''
}
readOnly={node.id !== pendingApprovalNodeId}
onChange={e =>
handleCommandChange(node.id, e.target.value)
}
/>
{node.id === pendingApprovalNodeId ? ( // Conditionally render Textarea or MarkdownFromCody
<Textarea
value={
modifiedCommands.get(node.id) ||
nodeResults.get(node.id) ||
''
}
readOnly={node.id !== pendingApprovalNodeId}
onChange={e =>
handleCommandChange(node.id, e.target.value)
}
/>
) : (
<SimpleMarkdown
handlePostMessage={handlePostMessage}
style={{
// Inline styles should now be valid
backgroundColor:
'var(--vscode-sideBar-background)',
border: '1px solid var(--vscode-panel-border)',
borderRadius:
'var(--vscode-widgets-borderWidth)',
padding: '10px',
marginBottom: '10px',
}}
>
{nodeResults.get(node.id) || ''}
</SimpleMarkdown>
)}
{node.type === NodeType.CLI &&
node.id === pendingApprovalNodeId && (
<div className="tw-flex tw-w-full tw-gap-2 tw-mt-2 tw-justify-center">
Expand Down
Loading

0 comments on commit f22f71f

Please sign in to comment.