feat(frontend): interface editor - tabs, inFlow graph canvas, inChat panel - #79
Open
maan-iitd2 wants to merge 1 commit into
Open
feat(frontend): interface editor - tabs, inFlow graph canvas, inChat panel#79maan-iitd2 wants to merge 1 commit into
maan-iitd2 wants to merge 1 commit into
Conversation
…panel Signed-off-by: maan-iitd2 <maan.iitd.ac.in@gmail.com>
There was a problem hiding this comment.
🟡 Changes recommended
Writer-shape errors, inconsistent graph mutations, unstable editor state, and chat races can corrupt or misrepresent configurations.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds InGen Studio’s interface editor with structured forms, a model-derived graph canvas, and conversational editing.
Changes:
- Adds source, column, processing, validation, and output editors.
- Adds inFlow graph visualization and editing controls.
- Adds interface management and inChat sessions.
File summaries
| File | Description |
|---|---|
tabs/ValidationsTab.jsx |
Edits column validations. |
tabs/SourcesTab.jsx |
Manages interface sources. |
tabs/PostProcessingTab.jsx |
Configures pivot processing. |
tabs/OutputTab.jsx |
Configures output writers. |
tabs/ColumnsTab.jsx |
Maps and formats columns. |
InterfaceManager.jsx |
Manages interfaces. |
InterfaceEditor.jsx |
Selects graph or chat view. |
graph/useGraphLayout.js |
Computes graph layout. |
graph/nodes/ValidationsNode.jsx |
Renders validation stage. |
graph/nodes/SourceNode.jsx |
Renders source nodes. |
graph/nodes/PreprocessNode.jsx |
Renders transform nodes. |
graph/nodes/PostprocessNode.jsx |
Renders post-processing stage. |
graph/nodes/OutputNode.jsx |
Renders output stage. |
graph/nodes/NodeShared.jsx |
Provides shared node UI. |
graph/nodes/ColumnsNode.jsx |
Renders column stage. |
graph/InterfaceGraphEditor.jsx |
Implements inFlow canvas. |
graph/GraphToolbar.jsx |
Adds graph controls. |
graph/graphConstants.js |
Defines graph contracts and styling. |
graph/ContextMenu.jsx |
Adds graph context actions. |
chat/InterfaceChatEditor.jsx |
Implements conversational editing. |
Review details
Suppressed comments (2)
frontend/src/components/editor/graph/InterfaceGraphEditor.jsx:441
- “Disconnect all edges” only filters the local ReactFlow edge array; it does not clear the source fields represented by feed edges or change the base-source chain. The YAML is unchanged and the edges reappear on the next model update. Persist each supported disconnection through the config model, or do not offer this action for derived edges/nodes.
const ctxDisconnect = ctxMenu?.nodeId
? () => {
setEdges((eds) => eds.filter((e) => e.source !== ctxMenu.nodeId && e.target !== ctxMenu.nodeId));
closeCtx();
frontend/src/components/editor/chat/InterfaceChatEditor.jsx:214
- The text input and Send button are disabled while a request is pending, but suggestion chips remain active and
handleSendMessagedoes not reject calls whileisTyping. Clicking another chip starts overlapping requests; the first completion clears the single boolean spinner while another request is still running, and replies can appear out of order. Disable chips while typing (and guard the handler) or track requests individually.
{chips.map((c) => (
<button key={c} className="prompt-chip" onClick={() => handleSendMessage(c)}>
{c === 'explain' ? <><Sparkles size={11} /> explain</> : `+ ${c}`}
- Files reviewed: 20/20 changed files
- Comments generated: 12
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+153
to
+155
| const isConversation = (ops?.length ?? 0) === 0; | ||
| const text = modelReply && (changed || isConversation) ? modelReply : reply; | ||
| setMessages((prev) => [...prev, { id: `msg-reply-${Date.now()}`, sender: 'assistant', text }]); |
Comment on lines
+420
to
+423
| if (ctxMenu.nodeId?.startsWith(SRC)) { | ||
| const srcId = ctxMenu.nodeId.slice(SRC.length); | ||
| updateModel((m) => removeSource(m, srcId)); | ||
| apply((it) => ({ ...it, sources: (it.sources ?? []).filter((s) => s !== srcId) })); |
Comment on lines
+67
to
+70
| for (const [name, it] of Object.entries(next.interfacesByName)) { | ||
| const filtered = (it.sources ?? []).filter((s) => s !== srcId); | ||
| if (filtered.length !== (it.sources ?? []).length) { | ||
| next = { ...next, interfacesByName: { ...next.interfacesByName, [name]: { ...it, sources: filtered } } }; |
Comment on lines
+50
to
+56
| try { | ||
| updateModel((m) => renameInterface(m, editingName, next)); | ||
| // Navigate to the renamed interface if it was active. | ||
| if (editingName === activeIface) { | ||
| router.replace(`/configs/${configId}/interfaces/${encodeURIComponent(next)}`); | ||
| } | ||
| } catch { /* duplicate name — ignore */ } |
Comment on lines
+187
to
+191
| const flow = (id, source, target) => ({ | ||
| id, source, target, | ||
| type: 'smoothstep', | ||
| sourceHandle: 'out', targetHandle: 'in', | ||
| animated: true, style: FLOW_STYLE, |
Comment on lines
+565
to
+567
| selectionOnDrag | ||
| multiSelectionKeyCode="Shift" | ||
| deleteKeyCode={['Backspace', 'Delete']} |
Comment on lines
+252
to
+254
| // Keyed on the column identity, not the index: ColumnCard holds local UI state and | ||
| // an index key would carry that state to a different row when columns are reordered. | ||
| key={`${col.src_col_name ?? ''}->${col.dest_col_name ?? ''}`} |
Comment on lines
+12
to
+16
| const props = output.props && !Array.isArray(output.props) ? output.props : {}; | ||
|
|
||
| const apply = (fn) => updateInterface(interfaceName, fn); | ||
| // Reset props on type change so fields from the previous writer don't leak into the new one's YAML. | ||
| const setType = (type) => apply((it) => setField(it, 'output', type ? { type, props: {} } : {})); |
Comment on lines
+619
to
+620
| <button className="grapheditor__drawer-close" onClick={() => setSelectedNodeId(null)}> | ||
| <X size={16} /> |
Comment on lines
+52
to
+55
| <select className="field__input" value={safeTarget} onChange={(e) => setTarget(Number(e.target.value))}> | ||
| {columns.map((c, i) => <option key={`${colLabel(c)}:${i}`} value={i}>{colLabel(c)}</option>)} | ||
| </select> | ||
| <select className="field__input field__input--wide" value={expectation} onChange={(e) => setExpectation(e.target.value)}> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the interface editor — the part of InGen Studio where a config is actually authored. Three
ways to edit the same model: structured tabs, a visual graph canvas (inFlow), and a
conversational panel (inChat).
This is part 4 of the frontend series. All new files under
frontend/src/components/editor/— nothing existing is modified.editor/tabs/editor/graph/editor/chat/Screenshots
Source loader — pick a source type, configure it:
inFlow graph canvas — the pipeline as a directed graph:
Columns tab — mapping source columns to output columns, with per-column formatters:
inChat panel — the same model edited conversationally; note the YAML panel on the right updating from the same edits:
This builds on #76 and the two preceding frontend PRs, and CI will fail here until they merge.
These components import
@/models/*,@/serializers/*,@/services/*and@/state/*. Expected —not a defect in this branch.
The diff is conflict-free and independent: no file here is touched by any other open PR.
On inChat scope: the panel in this PR is UI only, and it works standalone in
mockmode. Thebackend endpoint it calls in
httpmode is a deliberate follow-up PR, held back so that thebackend wrapper (#75) can be reviewed on its own first. Nothing in this PR requires that endpoint to
exist — no new Python dependency is introduced here.
Design notes for reviewers
not parallel implementations — edits made in any one immediately appear in the others and in the
YAML panel. This is the single most important thing to sanity-check in review.
stays the source of truth, so a graph bug can't corrupt a config.
editor/tabs/vseditor/graph/— happy to break it in two on request.Testing
Verified on a local integration branch (
main+ #76 + the two preceding frontend PRs + this PR):npm run build— fullnext buildcompiles clean, all 6 routes generatednpm run test— 57/57 passnpm run devin mock mode — browser smoke pass: start screen → create pipeline → editor withsource loader and live YAML panel → run console, 0 console errors
pytest test/andpytest backend/tests/(42 passed), nonew failures; a real
python -m ingenCLI run and a real HTTP run through the backend bothcompleted with all five stages
okQuality gates
All green on the integration branch:
npm run lintnpm run testnpm run buildpytest test/pytest backend/tests/🤖 Generated with Claude Code