darabos commited on
Commit
2e10db6
Β·
1 Parent(s): 5005cac

Open code files from directory + related stuff.

Browse files
biome.json CHANGED
@@ -1,4 +1,7 @@
1
  {
 
 
 
2
  "formatter": {
3
  "ignore": ["**/node_modules/**", "**/dist/**"],
4
  "lineWidth": 100,
@@ -21,6 +24,7 @@
21
  "useKeyWithClickEvents": "off",
22
  "useValidAnchor": "off",
23
  "useButtonType": "off",
 
24
  "noNoninteractiveTabindex": "off"
25
  }
26
  }
 
1
  {
2
+ "files": {
3
+ "ignore": ["**/*.lynxkite.json"]
4
+ },
5
  "formatter": {
6
  "ignore": ["**/node_modules/**", "**/dist/**"],
7
  "lineWidth": 100,
 
24
  "useKeyWithClickEvents": "off",
25
  "useValidAnchor": "off",
26
  "useButtonType": "off",
27
+ "noAutofocus": "off",
28
  "noNoninteractiveTabindex": "off"
29
  }
30
  }
examples/{AIMO β†’ AIMO.lynxkite.json} RENAMED
File without changes
examples/{Airlines demo β†’ Airlines demo.lynxkite.json} RENAMED
File without changes
examples/{Bio demo β†’ Bio demo.lynxkite.json} RENAMED
File without changes
examples/{BioNeMo demo β†’ BioNeMo demo.lynxkite.json} RENAMED
File without changes
examples/{Generative drug screening β†’ Generative drug screening.lynxkite.json} RENAMED
File without changes
examples/{Graph RAG β†’ Graph RAG.lynxkite.json} RENAMED
File without changes
examples/{Image processing β†’ Image processing.lynxkite.json} RENAMED
File without changes
examples/{LynxScribe demo β†’ LynxScribe demo.lynxkite.json} RENAMED
File without changes
examples/{Model definition β†’ Model definition.lynxkite.json} RENAMED
File without changes
examples/{Model use β†’ Model use.lynxkite.json} RENAMED
File without changes
examples/{NetworkX demo β†’ NetworkX demo.lynxkite.json} RENAMED
File without changes
examples/{ODE-GNN experiment β†’ ODE-GNN experiment.lynxkite.json} RENAMED
File without changes
examples/{ODE-GNN β†’ ODE-GNN.lynxkite.json} RENAMED
File without changes
examples/{PyTorch demo β†’ PyTorch demo.lynxkite.json} RENAMED
File without changes
examples/{RAG chatbot app β†’ RAG chatbot app.lynxkite.json} RENAMED
File without changes
examples/Word2vec.lynxkite.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/{sql β†’ sql.lynxkite.json} RENAMED
File without changes
examples/word2vec.py CHANGED
@@ -5,17 +5,14 @@ import pandas as pd
5
  ENV = "LynxKite Graph Analytics"
6
 
7
 
8
- @op(ENV, "Word2vec for the top 1000 words", cache=True)
9
  def word2vec_1000():
10
  model = staticvectors.StaticVectors("neuml/word2vec-quantized")
11
- with open("wordlist.txt") as f:
12
- words = [w.strip() for w in f.read().strip().split("\n")]
13
- df = pd.DataFrame(
14
- {
15
- "word": words,
16
- "embedding": model.embeddings(words).tolist(),
17
- }
18
  )
 
19
  return df
20
 
21
 
 
5
  ENV = "LynxKite Graph Analytics"
6
 
7
 
8
+ @op(ENV, "Word2vec for the top 1000 words", slow=True)
9
  def word2vec_1000():
10
  model = staticvectors.StaticVectors("neuml/word2vec-quantized")
11
+ df = pd.read_csv(
12
+ "https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt",
13
+ names=["word"],
 
 
 
 
14
  )
15
+ df["embedding"] = model.embeddings(df.word.tolist()).tolist()
16
  return df
17
 
18
 
lynxkite-app/src/lynxkite_app/main.py CHANGED
@@ -84,6 +84,15 @@ class DirectoryEntry(pydantic.BaseModel):
84
  type: str
85
 
86
 
 
 
 
 
 
 
 
 
 
87
  @app.get("/api/dir/list")
88
  def list_dir(path: str):
89
  path = data_path / path
@@ -92,7 +101,7 @@ def list_dir(path: str):
92
  [
93
  DirectoryEntry(
94
  name=str(p.relative_to(data_path)),
95
- type="directory" if p.is_dir() else "workspace",
96
  )
97
  for p in path.iterdir()
98
  if not p.name.startswith(".")
 
84
  type: str
85
 
86
 
87
+ def _get_path_type(path: pathlib.Path) -> str:
88
+ if path.is_dir():
89
+ return "directory"
90
+ elif path.suffixes[-2:] == [".lynxkite", ".json"]:
91
+ return "workspace"
92
+ else:
93
+ return "file"
94
+
95
+
96
  @app.get("/api/dir/list")
97
  def list_dir(path: str):
98
  path = data_path / path
 
101
  [
102
  DirectoryEntry(
103
  name=str(p.relative_to(data_path)),
104
+ type=_get_path_type(p),
105
  )
106
  for p in path.iterdir()
107
  if not p.name.startswith(".")
lynxkite-app/web/index.html CHANGED
@@ -4,7 +4,6 @@
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>LynxKite 2025</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
 
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
7
  </head>
8
  <body>
9
  <div id="root"></div>
lynxkite-app/web/src/Directory.tsx CHANGED
@@ -15,9 +15,46 @@ import FolderPlus from "~icons/tabler/folder-plus";
15
  // @ts-ignore
16
  import Home from "~icons/tabler/home";
17
  // @ts-ignore
 
 
 
 
18
  import Trash from "~icons/tabler/trash";
19
  import logo from "./assets/logo.png";
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  const fetcher = (url: string) => fetch(url).then((res) => res.json());
22
 
23
  export default function () {
@@ -27,48 +64,41 @@ export default function () {
27
  dedupingInterval: 0,
28
  });
29
  const navigate = useNavigate();
30
- const [isCreatingDir, setIsCreatingDir] = useState(false);
31
- const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
32
 
33
  function link(item: DirectoryEntry) {
34
  if (item.type === "directory") {
35
  return `/dir/${item.name}`;
36
  }
37
- return `/edit/${item.name}`;
 
 
 
38
  }
39
 
40
  function shortName(item: DirectoryEntry) {
41
- return item.name.split("/").pop();
 
 
 
42
  }
43
 
44
- function newName(list: DirectoryEntry[], baseName = "Untitled") {
45
- let i = 0;
46
- while (true) {
47
- const name = `${baseName}${i ? ` ${i}` : ""}`;
48
- if (!list.find((item) => item.name === name)) {
49
- return name;
50
- }
51
- i++;
52
- }
53
  }
54
-
55
- function newWorkspaceIn(path: string, list: DirectoryEntry[], workspaceName?: string) {
56
  const pathSlash = path ? `${path}/` : "";
57
- const name = workspaceName || newName(list);
58
- navigate(`/edit/${pathSlash}${name}`, { replace: true });
59
  }
60
-
61
- async function newFolderIn(path: string, list: DirectoryEntry[], folderName?: string) {
62
- const name = folderName || newName(list, "New Folder");
63
  const pathSlash = path ? `${path}/` : "";
64
-
65
  const res = await fetch("/api/dir/mkdir", {
66
  method: "POST",
67
  headers: { "Content-Type": "application/json" },
68
- body: JSON.stringify({ path: pathSlash + name }),
69
  });
70
  if (res.ok) {
71
- navigate(`/dir/${pathSlash}${name}`);
72
  } else {
73
  alert("Failed to create folder.");
74
  }
@@ -105,84 +135,66 @@ export default function () {
105
  {list.data && (
106
  <>
107
  <div className="actions">
108
- <div className="new-workspace">
109
- {isCreatingWorkspace && (
110
- // @ts-ignore
111
- <form
112
- onSubmit={(e) => {
113
- e.preventDefault();
114
- newWorkspaceIn(
115
- path || "",
116
- list.data,
117
- (e.target as HTMLFormElement).workspaceName.value.trim(),
118
- );
119
- }}
120
- >
121
- <input
122
- type="text"
123
- name="workspaceName"
124
- defaultValue={newName(list.data)}
125
- placeholder={newName(list.data)}
126
- />
127
- </form>
128
- )}
129
- <button type="button" onClick={() => setIsCreatingWorkspace(true)}>
130
- <FolderPlus /> New workspace
131
- </button>
132
- </div>
133
-
134
- <div className="new-folder">
135
- {isCreatingDir && (
136
- // @ts-ignore
137
- <form
138
- onSubmit={(e) => {
139
- e.preventDefault();
140
- newFolderIn(
141
- path || "",
142
- list.data,
143
- (e.target as HTMLFormElement).folderName.value.trim(),
144
- );
145
- }}
146
- >
147
- <input
148
- type="text"
149
- name="folderName"
150
- defaultValue={newName(list.data)}
151
- placeholder={newName(list.data)}
152
- />
153
- </form>
154
- )}
155
- <button type="button" onClick={() => setIsCreatingDir(true)}>
156
- <FolderPlus /> New folder
157
- </button>
158
- </div>
159
  </div>
160
 
161
- {path && (
162
  <div className="breadcrumbs">
163
  <Link to="/dir/">
164
  <Home />
165
  </Link>{" "}
166
  <span className="current-folder">{path}</span>
 
167
  </div>
 
 
168
  )}
169
 
170
- {list.data.map((item: DirectoryEntry) => (
171
- <div key={item.name} className="entry">
172
- <Link key={link(item)} to={link(item)}>
173
- {item.type === "directory" ? <Folder /> : <File />}
174
- {shortName(item)}
175
- </Link>
176
- <button
177
- type="button"
178
- onClick={() => {
179
- deleteItem(item);
180
- }}
181
- >
182
- <Trash />
183
- </button>
184
- </div>
185
- ))}
 
 
 
 
 
 
 
 
 
186
  </>
187
  )}
188
  </div>{" "}
 
15
  // @ts-ignore
16
  import Home from "~icons/tabler/home";
17
  // @ts-ignore
18
+ import LayoutGrid from "~icons/tabler/layout-grid";
19
+ // @ts-ignore
20
+ import LayoutGridAdd from "~icons/tabler/layout-grid-add";
21
+ // @ts-ignore
22
  import Trash from "~icons/tabler/trash";
23
  import logo from "./assets/logo.png";
24
 
25
+ function EntryCreator(props: {
26
+ label: string;
27
+ icon: JSX.Element;
28
+ onCreate: (name: string) => void;
29
+ }) {
30
+ const [isCreating, setIsCreating] = useState(false);
31
+ return (
32
+ <>
33
+ {isCreating ? (
34
+ <form
35
+ onSubmit={(e) => {
36
+ e.preventDefault();
37
+ props.onCreate((e.target as HTMLFormElement).entryName.value.trim());
38
+ }}
39
+ >
40
+ <input
41
+ className="input input-ghost w-full"
42
+ autoFocus
43
+ type="text"
44
+ name="entryName"
45
+ onBlur={() => setIsCreating(false)}
46
+ placeholder={`${props.label} name`}
47
+ />
48
+ </form>
49
+ ) : (
50
+ <button type="button" onClick={() => setIsCreating(true)}>
51
+ {props.icon} {props.label}
52
+ </button>
53
+ )}
54
+ </>
55
+ );
56
+ }
57
+
58
  const fetcher = (url: string) => fetch(url).then((res) => res.json());
59
 
60
  export default function () {
 
64
  dedupingInterval: 0,
65
  });
66
  const navigate = useNavigate();
 
 
67
 
68
  function link(item: DirectoryEntry) {
69
  if (item.type === "directory") {
70
  return `/dir/${item.name}`;
71
  }
72
+ if (item.type === "workspace") {
73
+ return `/edit/${item.name}`;
74
+ }
75
+ return `/code/${item.name}`;
76
  }
77
 
78
  function shortName(item: DirectoryEntry) {
79
+ return item.name
80
+ .split("/")
81
+ .pop()
82
+ ?.replace(/[.]lynxkite[.]json$/, "");
83
  }
84
 
85
+ function newWorkspaceIn(path: string, workspaceName: string) {
86
+ const pathSlash = path ? `${path}/` : "";
87
+ navigate(`/edit/${pathSlash}${workspaceName}.lynxkite.json`, { replace: true });
 
 
 
 
 
 
88
  }
89
+ function newCodeFile(path: string, name: string) {
 
90
  const pathSlash = path ? `${path}/` : "";
91
+ navigate(`/code/${pathSlash}${name}`, { replace: true });
 
92
  }
93
+ async function newFolderIn(path: string, folderName: string) {
 
 
94
  const pathSlash = path ? `${path}/` : "";
 
95
  const res = await fetch("/api/dir/mkdir", {
96
  method: "POST",
97
  headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify({ path: pathSlash + folderName }),
99
  });
100
  if (res.ok) {
101
+ navigate(`/dir/${pathSlash}${folderName}`);
102
  } else {
103
  alert("Failed to create folder.");
104
  }
 
135
  {list.data && (
136
  <>
137
  <div className="actions">
138
+ <EntryCreator
139
+ onCreate={(name) => {
140
+ newWorkspaceIn(path || "", name);
141
+ }}
142
+ icon={<LayoutGridAdd />}
143
+ label="New workspace"
144
+ />
145
+ <EntryCreator
146
+ onCreate={(name) => {
147
+ newCodeFile(path || "", name);
148
+ }}
149
+ icon={<FilePlus />}
150
+ label="New code file"
151
+ />
152
+ <EntryCreator
153
+ onCreate={(name: string) => {
154
+ newFolderIn(path || "", name);
155
+ }}
156
+ icon={<FolderPlus />}
157
+ label="New folder"
158
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  </div>
160
 
161
+ {path ? (
162
  <div className="breadcrumbs">
163
  <Link to="/dir/">
164
  <Home />
165
  </Link>{" "}
166
  <span className="current-folder">{path}</span>
167
+ <title>{path}</title>
168
  </div>
169
+ ) : (
170
+ <title>LynxKite 2000:MM</title>
171
  )}
172
 
173
+ {list.data.map(
174
+ (item: DirectoryEntry) =>
175
+ !shortName(item)?.startsWith("__") && (
176
+ <div key={item.name} className="entry">
177
+ <Link key={link(item)} to={link(item)}>
178
+ {item.type === "directory" ? (
179
+ <Folder />
180
+ ) : item.type === "workspace" ? (
181
+ <LayoutGrid />
182
+ ) : (
183
+ <File />
184
+ )}
185
+ {shortName(item)}
186
+ </Link>
187
+ <button
188
+ type="button"
189
+ onClick={() => {
190
+ deleteItem(item);
191
+ }}
192
+ >
193
+ <Trash />
194
+ </button>
195
+ </div>
196
+ ),
197
+ )}
198
  </>
199
  )}
200
  </div>{" "}
lynxkite-app/web/src/index.css CHANGED
@@ -326,7 +326,14 @@ body {
326
  .actions {
327
  display: flex;
328
  justify-content: space-evenly;
 
 
329
  padding: 5px;
 
 
 
 
 
330
  }
331
 
332
  .actions a {
 
326
  .actions {
327
  display: flex;
328
  justify-content: space-evenly;
329
+ align-items: center;
330
+ height: 50px;
331
  padding: 5px;
332
+
333
+ form,
334
+ button {
335
+ flex: 1;
336
+ }
337
  }
338
 
339
  .actions a {
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -53,6 +53,10 @@ function LynxKiteFlow() {
53
  const [nodes, setNodes] = useState([] as Node[]);
54
  const [edges, setEdges] = useState([] as Edge[]);
55
  const { path } = useParams();
 
 
 
 
56
  const [state, setState] = useState({ workspace: {} as Workspace });
57
  const [message, setMessage] = useState(null as string | null);
58
  useEffect(() => {
@@ -320,7 +324,8 @@ function LynxKiteFlow() {
320
  <a className="logo" href="">
321
  <img alt="" src={favicon} />
322
  </a>
323
- <div className="ws-name">{path}</div>
 
324
  <EnvironmentSelector
325
  options={Object.keys(catalog.data || {})}
326
  value={state.workspace.env!}
 
53
  const [nodes, setNodes] = useState([] as Node[]);
54
  const [edges, setEdges] = useState([] as Edge[]);
55
  const { path } = useParams();
56
+ const shortPath = path!
57
+ .split("/")
58
+ .pop()!
59
+ .replace(/[.]lynxkite[.]json$/, "");
60
  const [state, setState] = useState({ workspace: {} as Workspace });
61
  const [message, setMessage] = useState(null as string | null);
62
  useEffect(() => {
 
324
  <a className="logo" href="">
325
  <img alt="" src={favicon} />
326
  </a>
327
+ <div className="ws-name">{shortPath}</div>
328
+ <title>{shortPath}</title>
329
  <EnvironmentSelector
330
  options={Object.keys(catalog.data || {})}
331
  value={state.workspace.env!}
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -196,13 +196,14 @@ class Op(BaseConfig):
196
  return res
197
 
198
 
199
- def op(env: str, name: str, *, view="basic", outputs=None, params=None, cache=False):
200
  """Decorator for defining an operation."""
201
 
202
  def decorator(func):
203
- if cache:
204
- func = mem.cache(func)
205
  sig = inspect.signature(func)
 
 
 
206
  # Positional arguments are inputs.
207
  inputs = {
208
  name: Input(name=name, type=param.annotation)
@@ -319,6 +320,7 @@ def slow(func):
319
  return wrapper
320
 
321
 
 
322
  CATALOGS_SNAPSHOTS: dict[str, Catalogs] = {}
323
 
324
 
 
196
  return res
197
 
198
 
199
+ def op(env: str, name: str, *, view="basic", outputs=None, params=None, slow=False):
200
  """Decorator for defining an operation."""
201
 
202
  def decorator(func):
 
 
203
  sig = inspect.signature(func)
204
+ if slow:
205
+ func = mem.cache(func)
206
+ func = _global_slow(func)
207
  # Positional arguments are inputs.
208
  inputs = {
209
  name: Input(name=name, type=param.annotation)
 
320
  return wrapper
321
 
322
 
323
+ _global_slow = slow # For access inside op().
324
  CATALOGS_SNAPSHOTS: dict[str, Catalogs] = {}
325
 
326