Spaces:
Running
Running
Allow coloring node titles.
Browse files- lynxkite-app/web/src/index.css +14 -20
- lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx +16 -3
- lynxkite-core/src/lynxkite/core/ops.py +14 -2
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch/pytorch_core.py +5 -2
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch/pytorch_ops.py +3 -2
lynxkite-app/web/src/index.css
CHANGED
@@ -104,27 +104,29 @@ body {
|
|
104 |
background-image: linear-gradient(
|
105 |
to right,
|
106 |
var(--status-color-1),
|
107 |
-
var(--status-color-2)
|
|
|
108 |
var(--status-color-3)
|
109 |
);
|
|
|
110 |
background-size: 180% 180%;
|
111 |
-
--status-color-1:
|
112 |
-
--status-color-2:
|
113 |
-
--status-color-3:
|
114 |
transition: --status-color-1 0.3s, --status-color-2 0.3s, --status-color-3 0.3s;
|
115 |
}
|
116 |
|
117 |
.lynxkite-node .title.active {
|
118 |
-
--status-color-1:
|
119 |
-
--status-color-2:
|
120 |
-
--status-color-3:
|
121 |
-
animation: active-node-gradient-animation 2s ease-in-out infinite;
|
122 |
}
|
123 |
|
124 |
.lynxkite-node .title.planned {
|
125 |
-
--status-color-1:
|
126 |
-
--status-color-2:
|
127 |
-
--status-color-3:
|
128 |
}
|
129 |
|
130 |
.handle-name {
|
@@ -405,15 +407,7 @@ body {
|
|
405 |
}
|
406 |
|
407 |
@keyframes active-node-gradient-animation {
|
408 |
-
|
409 |
-
background-position-x: 100%;
|
410 |
-
}
|
411 |
-
|
412 |
-
50% {
|
413 |
-
background-position-x: 0%;
|
414 |
-
}
|
415 |
-
|
416 |
-
100% {
|
417 |
background-position-x: 100%;
|
418 |
}
|
419 |
}
|
|
|
104 |
background-image: linear-gradient(
|
105 |
to right,
|
106 |
var(--status-color-1),
|
107 |
+
var(--status-color-2) 40%,
|
108 |
+
var(--status-color-2) 60%,
|
109 |
var(--status-color-3)
|
110 |
);
|
111 |
+
background-blend-mode: luminosity;
|
112 |
background-size: 180% 180%;
|
113 |
+
--status-color-1: #0000;
|
114 |
+
--status-color-2: #0000;
|
115 |
+
--status-color-3: #0000;
|
116 |
transition: --status-color-1 0.3s, --status-color-2 0.3s, --status-color-3 0.3s;
|
117 |
}
|
118 |
|
119 |
.lynxkite-node .title.active {
|
120 |
+
--status-color-1: #0000;
|
121 |
+
--status-color-2: #fff4;
|
122 |
+
--status-color-3: #888f;
|
123 |
+
animation: active-node-gradient-animation 1.2s alternate ease-in-out infinite;
|
124 |
}
|
125 |
|
126 |
.lynxkite-node .title.planned {
|
127 |
+
--status-color-1: #888f;
|
128 |
+
--status-color-2: #888f;
|
129 |
+
--status-color-3: #888f;
|
130 |
}
|
131 |
|
132 |
.handle-name {
|
|
|
407 |
}
|
408 |
|
409 |
@keyframes active-node-gradient-animation {
|
410 |
+
to {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
background-position-x: 100%;
|
412 |
}
|
413 |
}
|
lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx
CHANGED
@@ -43,6 +43,12 @@ function getHandles(inputs: object, outputs: object) {
|
|
43 |
return handles;
|
44 |
}
|
45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
|
47 |
const reactFlow = useReactFlow();
|
48 |
const data = props.data;
|
@@ -57,7 +63,10 @@ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
|
|
57 |
left: "top",
|
58 |
right: "top",
|
59 |
};
|
60 |
-
|
|
|
|
|
|
|
61 |
return (
|
62 |
<div
|
63 |
className={`node-container ${expanded ? "expanded" : "collapsed"} `}
|
@@ -67,7 +76,11 @@ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
|
|
67 |
}}
|
68 |
>
|
69 |
<div className="lynxkite-node" style={props.nodeStyle}>
|
70 |
-
<div
|
|
|
|
|
|
|
|
|
71 |
{data.title}
|
72 |
{data.error && <span className="title-icon">⚠️</span>}
|
73 |
{expanded || <span className="title-icon">⋯</span>}
|
@@ -97,7 +110,7 @@ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
|
|
97 |
)}
|
98 |
{handles.map((handle) => (
|
99 |
<Handle
|
100 |
-
key={handle.name}
|
101 |
id={handle.name}
|
102 |
type={handle.type}
|
103 |
position={handle.position as Position}
|
|
|
43 |
return handles;
|
44 |
}
|
45 |
|
46 |
+
const OP_COLORS: { [key: string]: string } = {
|
47 |
+
orange: "oklch(75% 0.2 55)",
|
48 |
+
blue: "oklch(75% 0.2 230)",
|
49 |
+
green: "oklch(75% 0.2 130)",
|
50 |
+
};
|
51 |
+
|
52 |
function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
|
53 |
const reactFlow = useReactFlow();
|
54 |
const data = props.data;
|
|
|
63 |
left: "top",
|
64 |
right: "top",
|
65 |
};
|
66 |
+
const titleStyle: { backgroundColor?: string } = {};
|
67 |
+
if (data.meta?.color) {
|
68 |
+
titleStyle.backgroundColor = OP_COLORS[data.meta.color] || data.meta.color;
|
69 |
+
}
|
70 |
return (
|
71 |
<div
|
72 |
className={`node-container ${expanded ? "expanded" : "collapsed"} `}
|
|
|
76 |
}}
|
77 |
>
|
78 |
<div className="lynxkite-node" style={props.nodeStyle}>
|
79 |
+
<div
|
80 |
+
className={`title bg-primary ${data.status}`}
|
81 |
+
style={titleStyle}
|
82 |
+
onClick={titleClicked}
|
83 |
+
>
|
84 |
{data.title}
|
85 |
{data.error && <span className="title-icon">⚠️</span>}
|
86 |
{expanded || <span className="title-icon">⋯</span>}
|
|
|
110 |
)}
|
111 |
{handles.map((handle) => (
|
112 |
<Handle
|
113 |
+
key={`${handle.name} on ${handle.position}`}
|
114 |
id={handle.name}
|
115 |
type={handle.type}
|
116 |
position={handle.position as Position}
|
lynxkite-core/src/lynxkite/core/ops.py
CHANGED
@@ -168,6 +168,7 @@ class Op(BaseConfig):
|
|
168 |
outputs: dict[str, Output]
|
169 |
# TODO: Make type an enum with the possible values.
|
170 |
type: str = "basic" # The UI to use for this operation.
|
|
|
171 |
|
172 |
def __call__(self, *inputs, **params):
|
173 |
# Convert parameters.
|
@@ -199,7 +200,16 @@ class Op(BaseConfig):
|
|
199 |
return res
|
200 |
|
201 |
|
202 |
-
def op(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
"""Decorator for defining an operation."""
|
204 |
|
205 |
def decorator(func):
|
@@ -234,6 +244,7 @@ def op(env: str, name: str, *, view="basic", outputs=None, params=None, slow=Fal
|
|
234 |
inputs=inputs,
|
235 |
outputs=_outputs,
|
236 |
type=_view,
|
|
|
237 |
)
|
238 |
CATALOGS.setdefault(env, {})
|
239 |
CATALOGS[env][name] = op
|
@@ -291,7 +302,7 @@ def no_op(*args, **kwargs):
|
|
291 |
return None
|
292 |
|
293 |
|
294 |
-
def register_passive_op(env: str, name: str, inputs=[], outputs=["output"], params=[]):
|
295 |
"""A passive operation has no associated code."""
|
296 |
op = Op(
|
297 |
func=no_op,
|
@@ -303,6 +314,7 @@ def register_passive_op(env: str, name: str, inputs=[], outputs=["output"], para
|
|
303 |
outputs=dict(
|
304 |
(o, Output(name=o, type=None)) if isinstance(o, str) else (o.name, o) for o in outputs
|
305 |
),
|
|
|
306 |
)
|
307 |
CATALOGS.setdefault(env, {})
|
308 |
CATALOGS[env][name] = op
|
|
|
168 |
outputs: dict[str, Output]
|
169 |
# TODO: Make type an enum with the possible values.
|
170 |
type: str = "basic" # The UI to use for this operation.
|
171 |
+
color: str = "orange" # The color of the operation in the UI.
|
172 |
|
173 |
def __call__(self, *inputs, **params):
|
174 |
# Convert parameters.
|
|
|
200 |
return res
|
201 |
|
202 |
|
203 |
+
def op(
|
204 |
+
env: str,
|
205 |
+
name: str,
|
206 |
+
*,
|
207 |
+
view="basic",
|
208 |
+
outputs=None,
|
209 |
+
params=None,
|
210 |
+
slow=False,
|
211 |
+
color=None,
|
212 |
+
):
|
213 |
"""Decorator for defining an operation."""
|
214 |
|
215 |
def decorator(func):
|
|
|
244 |
inputs=inputs,
|
245 |
outputs=_outputs,
|
246 |
type=_view,
|
247 |
+
color=color or "orange",
|
248 |
)
|
249 |
CATALOGS.setdefault(env, {})
|
250 |
CATALOGS[env][name] = op
|
|
|
302 |
return None
|
303 |
|
304 |
|
305 |
+
def register_passive_op(env: str, name: str, inputs=[], outputs=["output"], params=[], **kwargs):
|
306 |
"""A passive operation has no associated code."""
|
307 |
op = Op(
|
308 |
func=no_op,
|
|
|
314 |
outputs=dict(
|
315 |
(o, Output(name=o, type=None)) if isinstance(o, str) else (o.name, o) for o in outputs
|
316 |
),
|
317 |
+
**kwargs,
|
318 |
)
|
319 |
CATALOGS.setdefault(env, {})
|
320 |
CATALOGS[env][name] = op
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch/pytorch_core.py
CHANGED
@@ -13,7 +13,9 @@ from .. import core
|
|
13 |
ENV = "PyTorch model"
|
14 |
|
15 |
|
16 |
-
def op(name, **kwargs):
|
|
|
|
|
17 |
_op = ops.op(ENV, name, **kwargs)
|
18 |
|
19 |
def decorator(func):
|
@@ -28,7 +30,7 @@ def op(name, **kwargs):
|
|
28 |
return decorator
|
29 |
|
30 |
|
31 |
-
def reg(name, inputs=[], outputs=None, params=[]):
|
32 |
if outputs is None:
|
33 |
outputs = inputs
|
34 |
return ops.register_passive_op(
|
@@ -37,6 +39,7 @@ def reg(name, inputs=[], outputs=None, params=[]):
|
|
37 |
inputs=[ops.Input(name=name, position="bottom", type="tensor") for name in inputs],
|
38 |
outputs=[ops.Output(name=name, position="top", type="tensor") for name in outputs],
|
39 |
params=params,
|
|
|
40 |
)
|
41 |
|
42 |
|
|
|
13 |
ENV = "PyTorch model"
|
14 |
|
15 |
|
16 |
+
def op(name, weights=False, **kwargs):
|
17 |
+
if weights:
|
18 |
+
kwargs["color"] = "blue"
|
19 |
_op = ops.op(ENV, name, **kwargs)
|
20 |
|
21 |
def decorator(func):
|
|
|
30 |
return decorator
|
31 |
|
32 |
|
33 |
+
def reg(name, inputs=[], outputs=None, params=[], **kwargs):
|
34 |
if outputs is None:
|
35 |
outputs = inputs
|
36 |
return ops.register_passive_op(
|
|
|
39 |
inputs=[ops.Input(name=name, position="bottom", type="tensor") for name in inputs],
|
40 |
outputs=[ops.Output(name=name, position="top", type="tensor") for name in outputs],
|
41 |
params=params,
|
42 |
+
**kwargs,
|
43 |
)
|
44 |
|
45 |
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch/pytorch_ops.py
CHANGED
@@ -13,7 +13,7 @@ reg("Input: sequential", outputs=["y"])
|
|
13 |
reg("Output", inputs=["x"], outputs=["x"], params=[P.basic("name")])
|
14 |
|
15 |
|
16 |
-
@op("LSTM")
|
17 |
def lstm(x, *, input_size=1024, hidden_size=1024, dropout=0.0):
|
18 |
return torch.nn.LSTM(input_size, hidden_size, dropout=0.0)
|
19 |
|
@@ -59,7 +59,7 @@ def dropout(x, *, p=0.0):
|
|
59 |
return torch.nn.Dropout(p)
|
60 |
|
61 |
|
62 |
-
@op("Linear")
|
63 |
def linear(x, *, output_dim=1024):
|
64 |
return pyg_nn.Linear(-1, output_dim)
|
65 |
|
@@ -124,6 +124,7 @@ reg(
|
|
124 |
),
|
125 |
P.basic("lr", 0.001),
|
126 |
],
|
|
|
127 |
)
|
128 |
|
129 |
ops.register_passive_op(
|
|
|
13 |
reg("Output", inputs=["x"], outputs=["x"], params=[P.basic("name")])
|
14 |
|
15 |
|
16 |
+
@op("LSTM", weights=True)
|
17 |
def lstm(x, *, input_size=1024, hidden_size=1024, dropout=0.0):
|
18 |
return torch.nn.LSTM(input_size, hidden_size, dropout=0.0)
|
19 |
|
|
|
59 |
return torch.nn.Dropout(p)
|
60 |
|
61 |
|
62 |
+
@op("Linear", weights=True)
|
63 |
def linear(x, *, output_dim=1024):
|
64 |
return pyg_nn.Linear(-1, output_dim)
|
65 |
|
|
|
124 |
),
|
125 |
P.basic("lr", 0.001),
|
126 |
],
|
127 |
+
color="green",
|
128 |
)
|
129 |
|
130 |
ops.register_passive_op(
|