Skip to content

Commit d93fd87

Browse files
committed
Improvements
Former-commit-id: 5d568d4be233189d093ce7e541e04dcafce20a20 [formerly c4560ded28eae63cbcb4ecc11cba7eabcdb10090 [formerly b7cf8c0d53016bb70ea8950c6ba9f34f193543e3]] Former-commit-id: c4560ded28eae63cbcb4ecc11cba7eabcdb10090 Former-commit-id: 5600dc4a81f6e7b7d564d817f8213e746804ca96
1 parent 94b0d7d commit d93fd87

34 files changed

+889
-93
lines changed

.pnp.cjs

+235
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:01ae1232158c401f2abd237f0370a65cdf45df0ed8f4abcdf9c8d184de67760b
3+
size 115173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:210423b49a857764a74a2e0b525ec6e8ccca7c87812cabf02fe27af2259f742c
3+
size 14737
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:16a3038cac13e97042472b0503550cd5bc4bae72ffb1891f266a92793960e78a
3+
size 36595
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:fb8b7e5377b39fc8fe3f0ab636c94fa91b7538398c261c0bfc8fd2349b6e77a7
3+
size 46185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:8e76b521d65d4b84c69f47babfa19921838b4b3e41cbaabc1c6ba28409ffce6f
3+
size 918574
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c66dcb3d73241da2b4834c4641b80b068552026e70fdd8612659f5169cb9b822
3+
size 13136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:1221c37108dfe4b70bfffcd670b68da1561b29c5520c719f905c8b657d58cfd1
3+
size 93299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:8cbad490db7d18837ea25cd2f9e3ac3eb42a6960c67c0a7c47b80ff422c80ff7
3+
size 39862
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c2e31b723099961d9e0ed458662eb6f2df8bc7dc8d40a71b408815bffeb61313
3+
size 707630
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:58283acc105f9b4cb8ea76ea155f26ba52ed729e312b58520bfc0552c11307d6
3+
size 10361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c05ec38a7b05f31c36c7d0e3970fab230f5848434c4421357e935e9ba212ef9e
3+
size 8653

context.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
I am working on developing an AI storyboarding tool that allows users to create a series of prompts for a language model in a choose-your-own-adventure format. The tool is inspired by node-based editors, like the one found in Blender, where users can create nodes on a page that have inputs and outputs that can be connected by wires to form a web of connections between the prompts and the AI. Each node can be edited, and when editing, a larger window pops up with a text editor where users can tweak various aspects of the prompt that will be fed to the AI. This tool will provide a user-friendly interface for crafting interactive stories with an AI language model. Here is a tree of my current files for context. If you would like the contents of any of these files, please ask. The app is dark-themed and the colors are available in index.css.
1+
I am working on developing an AI storyboarding tool that allows users to create a series of prompts for a language model in a choose-your-own-adventure format. The tool is inspired by node-based editors, like the one found in Blender, where users can create nodes on a page that have inputs and outputs that can be connected by wires to form a web of connections between the prompts and the AI. Each node can be edited, and when editing, a larger window pops up with a text editor where users can tweak various aspects of the prompt that will be fed to the AI. This tool will provide a user-friendly interface for crafting interactive stories with an AI language model. Here is a tree of my current files for context. If you would like the contents of any of these files, please ask. The app is dark-themed and the colors are available in index.css. I'm using Emotion for CSS.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
]
2929
},
3030
"dependencies": {
31+
"@atlaskit/toggle": "^12.5.8",
3132
"@babel/core": "^7.16.0",
3233
"@emotion/react": "^11.10.6",
3334
"@emotion/styled": "^11.10.6",

src/App.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { RecoilRoot } from 'recoil';
22
import { GraphBuilder } from './components/GraphBuilder';
3+
import { MenuBar } from './components/MenuBar';
34

45
function App() {
56
return (
67
<RecoilRoot>
7-
<GraphBuilder />
8+
<div className="app">
9+
<MenuBar />
10+
<GraphBuilder />
11+
</div>
812
</RecoilRoot>
913
);
1014
}

src/components/ContextMenu.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const BlankAreaContextMenu: FC<Pick<ContextMenuProps, 'data' | 'onMenuItemSelect
195195
<MenuItem label="Add" hasSubMenu={true}>
196196
<MenuItem label="Prompt" onClick={() => addNode('prompt')} />
197197
{/* <MenuItem label="Branch" onClick={() => addNode('branch')} /> */}
198-
{/* <MenuItem label="Chat" onClick={() => addNode('chat')} /> */}
198+
<MenuItem label="Chat" onClick={() => addNode('chat')} />
199199
{/* <MenuItem label="Concat" onClick={() => addNode('concat')} /> */}
200200
{/* <MenuItem label="Interpolate" onClick={() => addNode('interpolate')} /> */}
201201
{/* <MenuItem label="User Input" onClick={() => addNode('userInput')} /> */}

src/components/DraggableNode.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { CSSProperties, HTMLAttributes, forwardRef, useCallback, MouseEvent, FC,
1111
import clsx from 'clsx';
1212
import { Nodes, createNodeInstance, createUnknownNodeInstance } from '../model/Nodes';
1313
import { ReactComponent as SettingsCogIcon } from 'majesticons/line/settings-cog-line.svg';
14-
import { NodeImpl } from '../model/NodeImpl';
1514
import { match } from 'ts-pattern';
1615
import { PromptNodeBody } from './nodeBodies/PromptNodeBody';
1716
import { PromptNode } from '../model/nodes/PromptNode';
17+
import { ChatNode } from '../model/nodes/ChatNode';
18+
import { ChatNodeBody } from './nodeBodies/ChatNodeBody';
1819

1920
interface DraggableNodeProps {
2021
node: ChartNode<string, unknown>;
@@ -152,7 +153,7 @@ export const ViewNode = memo(
152153
<div className="node-body">{body}</div>
153154
<div className="node-ports">
154155
<div className="input-ports">
155-
{nodeImpl.getInputDefinitions().map((input) => {
156+
{nodeImpl.getInputDefinitions(connections).map((input) => {
156157
const connected = connections.some((conn) => conn.inputNodeId === node.id && conn.inputId === input.id);
157158
return (
158159
<div key={input.id} className={clsx('port', { connected })}>
@@ -168,7 +169,7 @@ export const ViewNode = memo(
168169
})}
169170
</div>
170171
<div className="output-ports">
171-
{nodeImpl.getOutputDefinitions().map((output) => {
172+
{nodeImpl.getOutputDefinitions(connections).map((output) => {
172173
const connected = connections.some(
173174
(conn) => conn.outputNodeId === node.id && conn.outputId === output.id,
174175
);
@@ -196,16 +197,17 @@ export function getNodePortPosition(
196197
nodes: ChartNode<string, unknown>[],
197198
nodeId: NodeId,
198199
portId: PortId,
200+
getConnectionsForNode: (node: ChartNode<string, unknown>) => NodeConnection[],
199201
): { x: number; y: number } {
200202
const node = nodes.find((node) => node.id === nodeId);
201203
if (node && portId) {
202204
const nodeImpl = createUnknownNodeInstance(node);
203205
let isInput = true;
204-
const foundInput = nodeImpl.getInputDefinitions().find((input) => input.id === portId);
206+
const foundInput = nodeImpl.getInputDefinitions(getConnectionsForNode(node)).find((input) => input.id === portId);
205207
let foundPort: NodeInputDefinition | NodeOutputDefinition | undefined = foundInput;
206208
if (!foundPort) {
207209
isInput = false;
208-
foundPort = nodeImpl.getOutputDefinitions().find((output) => output.id === portId);
210+
foundPort = nodeImpl.getOutputDefinitions(getConnectionsForNode(node)).find((output) => output.id === portId);
209211
}
210212

211213
if (foundPort) {
@@ -227,5 +229,6 @@ export function getNodePortPosition(
227229
function getNodeBody(node: ChartNode<string, unknown>) {
228230
return match(node)
229231
.with({ type: 'prompt' }, (node) => <PromptNodeBody node={node as PromptNode} />)
232+
.with({ type: 'chat' }, (node) => <ChatNodeBody node={node as ChatNode} />)
230233
.otherwise(() => <div>Unknown node type</div>);
231234
}

src/components/GraphBuilder.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import styled from '@emotion/styled';
99
import { NodeType, nodeFactory } from '../model/Nodes';
1010
import { NodeId } from '../model/NodeBase';
1111
import { ContextMenuData } from '../hooks/useContextMenu';
12+
import { useCanvasPositioning } from '../hooks/useCanvasPositioning';
1213

1314
const Container = styled.div`
1415
position: relative;
@@ -18,6 +19,7 @@ export const GraphBuilder: FC = () => {
1819
const [nodes, setNodes] = useRecoilState(nodesSelector);
1920
const [connections, setConnections] = useRecoilState(connectionsSelector);
2021
const [selectedNode, setSelectedNode] = useRecoilState(selectedNodeState);
22+
const { clientToCanvasPosition } = useCanvasPositioning();
2123

2224
const addNode = (nodeType: NodeType, position: { x: number; y: number }) => {
2325
const newNode = nodeFactory(nodeType);
@@ -43,7 +45,7 @@ export const GraphBuilder: FC = () => {
4345
const contextMenuItemSelected = (menuItemId: string, contextMenuData: ContextMenuData) => {
4446
if (menuItemId.startsWith('Add:')) {
4547
const nodeType = menuItemId.substring(4) as NodeType;
46-
addNode(nodeType, { x: contextMenuData.x, y: contextMenuData.y });
48+
addNode(nodeType, clientToCanvasPosition(contextMenuData.x, contextMenuData.y));
4749
return;
4850
}
4951

src/components/MenuBar.tsx

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { css } from '@emotion/react';
2+
import { FC } from 'react';
3+
import { ReactComponent as ChevronRightIcon } from 'majesticons/line/chevron-right-line.svg';
4+
5+
const styles = css`
6+
display: flex;
7+
justify-content: space-between;
8+
align-items: center;
9+
10+
background-color: #2b2b2b;
11+
border-bottom: 1px solid var(--grey);
12+
z-index: 100;
13+
position: absolute;
14+
top: 0;
15+
left: 0;
16+
right: 0;
17+
height: 32px;
18+
19+
.dropdown-menu .dropdown-button {
20+
background-color: transparent;
21+
color: #ffffff;
22+
border: none;
23+
padding: 0.5rem 1rem;
24+
cursor: pointer;
25+
26+
&:hover {
27+
background-color: var(--grey);
28+
}
29+
}
30+
31+
.run-button button {
32+
background-color: var(--success);
33+
color: #ffffff;
34+
border: none;
35+
padding: 0.5rem 1rem;
36+
cursor: pointer;
37+
display: flex;
38+
align-items: center;
39+
gap: 0.5rem;
40+
41+
&:hover {
42+
background-color: var(--success-dark);
43+
}
44+
}
45+
`;
46+
47+
export const MenuBar: FC = () => {
48+
return (
49+
<div css={styles}>
50+
<div className="dropdown-menu">
51+
<button className="dropdown-button">File</button>
52+
</div>
53+
<div className="run-button">
54+
<button>
55+
Run <ChevronRightIcon />
56+
</button>
57+
</div>
58+
</div>
59+
);
60+
};

src/components/NodeCanvas.tsx

+21-21
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DndContext, DragOverlay, useDroppable } from '@dnd-kit/core';
22
import { DraggableNode, ViewNode } from './DraggableNode';
33
import { css } from '@emotion/react';
44
import { nodeStyles } from './nodeStyles';
5-
import { FC, MutableRefObject, useCallback, useMemo, useRef, useState } from 'react';
5+
import { FC, useCallback, useMemo, useState } from 'react';
66
import { ContextMenu } from './ContextMenu';
77
import { CSSTransition } from 'react-transition-group';
88
import { WireLayer } from './WireLayer';
@@ -34,6 +34,7 @@ const styles = css`
3434
background-size: 20px 20px;
3535
background-position: -1px -1px;
3636
overflow: hidden;
37+
z-index: 0;
3738
3839
.nodes {
3940
position: relative;
@@ -104,8 +105,8 @@ export const NodeCanvas: FC<NodeCanvasProps> = ({
104105
const [canvasPosition, setCanvasPosition] = useRecoilState(canvasPositionState);
105106
const [isDraggingCanvas, setIsDraggingCanvas] = useState(false);
106107
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
107-
const { clientToCanvasPosition, canvasToClientPosition } = useCanvasPositioning();
108-
const [lastMousePosition, setLastMousePosition] = useState({ x: 0, y: 0 });
108+
const { clientToCanvasPosition } = useCanvasPositioning();
109+
const [, setLastMousePosition] = useState({ x: 0, y: 0 });
109110

110111
const { draggingNode, onNodeStartDrag, onNodeDragged } = useDraggingNode(nodes, onNodesChanged);
111112
const { draggingWire, onWireStartDrag, onWireEndDrag } = useDraggingWire(nodes, connections, onConnectionsChanged);
@@ -209,7 +210,7 @@ export const NodeCanvas: FC<NodeCanvasProps> = ({
209210
onMouseLeave={canvasMouseUp}
210211
onWheel={handleZoom}
211212
style={{
212-
backgroundPosition: `${(canvasPosition.x % 20) - 1}px ${(canvasPosition.y % 20) - 1}px`,
213+
backgroundPosition: `${canvasPosition.x - 1}px ${canvasPosition.y - 1}px`,
213214
backgroundSize: `${20 * canvasPosition.zoom}px ${20 * canvasPosition.zoom}px`,
214215
}}
215216
>
@@ -246,24 +247,23 @@ export const NodeCanvas: FC<NodeCanvasProps> = ({
246247
/>
247248
))}
248249
</div>
249-
250-
<CSSTransition
251-
nodeRef={contextMenuRef}
252-
in={showContextMenu}
253-
timeout={200}
254-
classNames="context-menu"
255-
unmountOnExit
256-
onExited={() => setContextMenuData({ x: 0, y: 0, data: null })}
257-
>
258-
<ContextMenu
259-
ref={contextMenuRef}
260-
x={contextMenuData.x}
261-
y={contextMenuData.y}
262-
data={contextMenuData.data}
263-
onMenuItemSelected={contextMenuItemSelected}
264-
/>
265-
</CSSTransition>
266250
</div>
251+
<CSSTransition
252+
nodeRef={contextMenuRef}
253+
in={showContextMenu}
254+
timeout={200}
255+
classNames="context-menu"
256+
unmountOnExit
257+
onExited={() => setContextMenuData({ x: 0, y: 0, data: null })}
258+
>
259+
<ContextMenu
260+
ref={contextMenuRef}
261+
x={contextMenuData.x}
262+
y={contextMenuData.y}
263+
data={contextMenuData.data}
264+
onMenuItemSelected={contextMenuItemSelected}
265+
/>
266+
</CSSTransition>
267267
<WireLayer nodes={nodes} connections={connections} draggingWire={draggingWire} />
268268

269269
<DragOverlay dropAnimation={null}>

src/components/NodeEditor.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { match } from 'ts-pattern';
1010
import { PromptNodeEditor } from './nodeEditors/PromptNodeEditor';
1111
import produce from 'immer';
1212
import { InlineEditableTextArea } from './InlineEditableTextArea';
13+
import { ChatNodeEditor } from './nodeEditors/ChatNodeEditor';
1314

1415
export const NodeEditorRenderer: FC = () => {
1516
const nodes = useRecoilValue(nodesSelector);
@@ -28,7 +29,7 @@ type NodeEditorProps = { selectedNode: ChartNode<string, unknown> };
2829

2930
const Container = styled.div`
3031
position: absolute;
31-
top: 0;
32+
top: 32px;
3233
right: 0;
3334
width: 45%;
3435
min-width: 500px;
@@ -124,6 +125,10 @@ const Container = styled.div`
124125
flex-grow: 1;
125126
position: relative;
126127
}
128+
129+
.unknown-node {
130+
color: var(--primary);
131+
}
127132
`;
128133

129134
export const NodeEditor: FC<NodeEditorProps> = () => {
@@ -146,7 +151,8 @@ export const NodeEditor: FC<NodeEditorProps> = () => {
146151

147152
const nodeEditor = match(selectedNode)
148153
.with({ type: 'prompt' }, (node) => <PromptNodeEditor node={node} onChange={(node) => updateNode(node)} />)
149-
.otherwise(() => <div>Unknown node type</div>);
154+
.with({ type: 'chat' }, (node) => <ChatNodeEditor node={node} onChange={(node) => updateNode(node)} />)
155+
.otherwise(() => <div className="unknown-node">Unknown node type</div>);
150156

151157
useEffect(() => {
152158
const handleKeyDown = (event: KeyboardEvent) => {

0 commit comments

Comments
 (0)