Spaces:
Running
Running
Load old files, few fixes for LynxScribe demo.
Browse files- server/crdt.py +19 -10
- server/executors/one_by_one.py +8 -3
- server/lynxkite_ops.py +1 -1
- server/lynxscribe_ops.py +2 -2
- server/main.py +2 -2
- server/workspace.py +2 -2
- web/src/NodeWithTableView.svelte +7 -5
server/crdt.py
CHANGED
@@ -32,6 +32,7 @@ class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
|
32 |
ws['edges'] = pycrdt.Array()
|
33 |
if 'env' not in ws:
|
34 |
ws['env'] = 'unset'
|
|
|
35 |
room = pycrdt_websocket.YRoom(ystore=ystore, ydoc=ydoc)
|
36 |
room.ws = ws
|
37 |
def on_change(changes):
|
@@ -59,39 +60,47 @@ def clean_input(ws_pyd):
|
|
59 |
for key in list(node.model_extra.keys()):
|
60 |
delattr(node, key)
|
61 |
|
62 |
-
def crdt_update(crdt_obj, python_obj):
|
63 |
if isinstance(python_obj, dict):
|
64 |
for key, value in python_obj.items():
|
65 |
-
if
|
|
|
|
|
66 |
if crdt_obj.get(key) is None:
|
67 |
crdt_obj[key] = pycrdt.Map()
|
68 |
-
crdt_update(crdt_obj[key], value)
|
69 |
elif isinstance(value, list):
|
70 |
if crdt_obj.get(key) is None:
|
71 |
crdt_obj[key] = pycrdt.Array()
|
72 |
-
crdt_update(crdt_obj[key], value)
|
73 |
else:
|
74 |
-
print('set', key, value)
|
75 |
crdt_obj[key] = value
|
76 |
elif isinstance(python_obj, list):
|
77 |
for i, value in enumerate(python_obj):
|
78 |
if isinstance(value, dict):
|
79 |
if i >= len(crdt_obj):
|
80 |
crdt_obj.append(pycrdt.Map())
|
81 |
-
crdt_update(crdt_obj[i], value)
|
82 |
elif isinstance(value, list):
|
83 |
if i >= len(crdt_obj):
|
84 |
crdt_obj.append(pycrdt.Array())
|
85 |
-
crdt_update(crdt_obj[i], value)
|
86 |
else:
|
87 |
if i >= len(crdt_obj):
|
88 |
crdt_obj.append(value)
|
89 |
else:
|
90 |
-
print('set', i, value)
|
91 |
crdt_obj[i] = value
|
92 |
else:
|
93 |
raise ValueError('Invalid type:', python_obj)
|
94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
async def workspace_changed(e, ws_crdt):
|
96 |
global last_ws_input
|
97 |
from . import workspace
|
@@ -99,14 +108,14 @@ async def workspace_changed(e, ws_crdt):
|
|
99 |
clean_input(ws_pyd)
|
100 |
if ws_pyd == last_ws_input:
|
101 |
return
|
102 |
-
print('ws changed')
|
103 |
last_ws_input = ws_pyd.model_copy(deep=True)
|
104 |
-
workspace.execute(ws_pyd)
|
105 |
for nc, np in zip(ws_crdt['nodes'], ws_pyd.nodes):
|
106 |
if 'data' not in nc:
|
107 |
nc['data'] = pycrdt.Map()
|
108 |
# Display is added as an opaque Box.
|
109 |
nc['data']['display'] = np.data.display
|
|
|
110 |
|
111 |
@contextlib.asynccontextmanager
|
112 |
async def lifespan(app):
|
|
|
32 |
ws['edges'] = pycrdt.Array()
|
33 |
if 'env' not in ws:
|
34 |
ws['env'] = 'unset'
|
35 |
+
try_to_load_workspace(ws, name)
|
36 |
room = pycrdt_websocket.YRoom(ystore=ystore, ydoc=ydoc)
|
37 |
room.ws = ws
|
38 |
def on_change(changes):
|
|
|
60 |
for key in list(node.model_extra.keys()):
|
61 |
delattr(node, key)
|
62 |
|
63 |
+
def crdt_update(crdt_obj, python_obj, boxes=set()):
|
64 |
if isinstance(python_obj, dict):
|
65 |
for key, value in python_obj.items():
|
66 |
+
if key in boxes:
|
67 |
+
crdt_obj[key] = value
|
68 |
+
elif isinstance(value, dict):
|
69 |
if crdt_obj.get(key) is None:
|
70 |
crdt_obj[key] = pycrdt.Map()
|
71 |
+
crdt_update(crdt_obj[key], value, boxes)
|
72 |
elif isinstance(value, list):
|
73 |
if crdt_obj.get(key) is None:
|
74 |
crdt_obj[key] = pycrdt.Array()
|
75 |
+
crdt_update(crdt_obj[key], value, boxes)
|
76 |
else:
|
|
|
77 |
crdt_obj[key] = value
|
78 |
elif isinstance(python_obj, list):
|
79 |
for i, value in enumerate(python_obj):
|
80 |
if isinstance(value, dict):
|
81 |
if i >= len(crdt_obj):
|
82 |
crdt_obj.append(pycrdt.Map())
|
83 |
+
crdt_update(crdt_obj[i], value, boxes)
|
84 |
elif isinstance(value, list):
|
85 |
if i >= len(crdt_obj):
|
86 |
crdt_obj.append(pycrdt.Array())
|
87 |
+
crdt_update(crdt_obj[i], value, boxes)
|
88 |
else:
|
89 |
if i >= len(crdt_obj):
|
90 |
crdt_obj.append(value)
|
91 |
else:
|
|
|
92 |
crdt_obj[i] = value
|
93 |
else:
|
94 |
raise ValueError('Invalid type:', python_obj)
|
95 |
|
96 |
+
|
97 |
+
def try_to_load_workspace(ws, name):
|
98 |
+
from . import workspace
|
99 |
+
json_path = f'data/{name}'
|
100 |
+
if os.path.exists(json_path):
|
101 |
+
ws_pyd = workspace.load(json_path)
|
102 |
+
crdt_update(ws, ws_pyd.model_dump(), boxes={'display'})
|
103 |
+
|
104 |
async def workspace_changed(e, ws_crdt):
|
105 |
global last_ws_input
|
106 |
from . import workspace
|
|
|
108 |
clean_input(ws_pyd)
|
109 |
if ws_pyd == last_ws_input:
|
110 |
return
|
|
|
111 |
last_ws_input = ws_pyd.model_copy(deep=True)
|
112 |
+
await workspace.execute(ws_pyd)
|
113 |
for nc, np in zip(ws_crdt['nodes'], ws_pyd.nodes):
|
114 |
if 'data' not in nc:
|
115 |
nc['data'] = pycrdt.Map()
|
116 |
# Display is added as an opaque Box.
|
117 |
nc['data']['display'] = np.data.display
|
118 |
+
nc['data']['error'] = np.data.error
|
119 |
|
120 |
@contextlib.asynccontextmanager
|
121 |
async def lifespan(app):
|
server/executors/one_by_one.py
CHANGED
@@ -74,7 +74,12 @@ def make_cache_key(obj):
|
|
74 |
|
75 |
EXECUTOR_OUTPUT_CACHE = {}
|
76 |
|
77 |
-
def
|
|
|
|
|
|
|
|
|
|
|
78 |
nodes = {n.id: n for n in ws.nodes}
|
79 |
contexts = {n.id: Context(node=n) for n in ws.nodes}
|
80 |
edges = {n.id: [] for n in ws.nodes}
|
@@ -113,10 +118,10 @@ def execute(ws, catalog, cache=None):
|
|
113 |
if cache is not None:
|
114 |
key = make_cache_key((inputs, params))
|
115 |
if key not in cache:
|
116 |
-
cache[key] = op(*inputs, **params)
|
117 |
result = cache[key]
|
118 |
else:
|
119 |
-
result = op(*inputs, **params)
|
120 |
except Exception as e:
|
121 |
traceback.print_exc()
|
122 |
data.error = str(e)
|
|
|
74 |
|
75 |
EXECUTOR_OUTPUT_CACHE = {}
|
76 |
|
77 |
+
async def await_if_needed(obj):
|
78 |
+
if inspect.isawaitable(obj):
|
79 |
+
return await obj
|
80 |
+
return obj
|
81 |
+
|
82 |
+
async def execute(ws, catalog, cache=None):
|
83 |
nodes = {n.id: n for n in ws.nodes}
|
84 |
contexts = {n.id: Context(node=n) for n in ws.nodes}
|
85 |
edges = {n.id: [] for n in ws.nodes}
|
|
|
118 |
if cache is not None:
|
119 |
key = make_cache_key((inputs, params))
|
120 |
if key not in cache:
|
121 |
+
cache[key] = await await_if_needed(op(*inputs, **params))
|
122 |
result = cache[key]
|
123 |
else:
|
124 |
+
result = await await_if_needed(op(*inputs, **params))
|
125 |
except Exception as e:
|
126 |
traceback.print_exc()
|
127 |
data.error = str(e)
|
server/lynxkite_ops.py
CHANGED
@@ -83,7 +83,7 @@ def disambiguate_edges(ws):
|
|
83 |
|
84 |
|
85 |
@ops.register_executor('LynxKite')
|
86 |
-
def execute(ws):
|
87 |
catalog = ops.CATALOGS['LynxKite']
|
88 |
# Nodes are responsible for interpreting/executing their child nodes.
|
89 |
nodes = [n for n in ws.nodes if not n.parentId]
|
|
|
83 |
|
84 |
|
85 |
@ops.register_executor('LynxKite')
|
86 |
+
async def execute(ws):
|
87 |
catalog = ops.CATALOGS['LynxKite']
|
88 |
# Nodes are responsible for interpreting/executing their child nodes.
|
89 |
nodes = [n for n in ws.nodes if not n.parentId]
|
server/lynxscribe_ops.py
CHANGED
@@ -121,10 +121,10 @@ def mask(*, name='', regex='', exceptions='', mask_pattern=''):
|
|
121 |
|
122 |
@ops.input_position(chat_api="bottom")
|
123 |
@op("Test Chat API")
|
124 |
-
def test_chat_api(message, chat_api, *, show_details=False):
|
125 |
chat_api = chat_api[0]['chat_api']
|
126 |
request = ChatAPIRequest(session_id="b43215a0-428f-11ef-9454-0242ac120002", question=message['text'], history=[])
|
127 |
-
response =
|
128 |
if show_details:
|
129 |
return {**response.__dict__}
|
130 |
else:
|
|
|
121 |
|
122 |
@ops.input_position(chat_api="bottom")
|
123 |
@op("Test Chat API")
|
124 |
+
async def test_chat_api(message, chat_api, *, show_details=False):
|
125 |
chat_api = chat_api[0]['chat_api']
|
126 |
request = ChatAPIRequest(session_id="b43215a0-428f-11ef-9454-0242ac120002", question=message['text'], history=[])
|
127 |
+
response = await chat_api.answer(request)
|
128 |
if show_details:
|
129 |
return {**response.__dict__}
|
130 |
else:
|
server/main.py
CHANGED
@@ -35,9 +35,9 @@ def save(req: SaveRequest):
|
|
35 |
workspace.save(req.ws, path)
|
36 |
|
37 |
@app.post("/api/save")
|
38 |
-
def save_and_execute(req: SaveRequest):
|
39 |
save(req)
|
40 |
-
workspace.execute(req.ws)
|
41 |
save(req)
|
42 |
return req.ws
|
43 |
|
|
|
35 |
workspace.save(req.ws, path)
|
36 |
|
37 |
@app.post("/api/save")
|
38 |
+
async def save_and_execute(req: SaveRequest):
|
39 |
save(req)
|
40 |
+
await workspace.execute(req.ws)
|
41 |
save(req)
|
42 |
return req.ws
|
43 |
|
server/workspace.py
CHANGED
@@ -43,9 +43,9 @@ class Workspace(BaseConfig):
|
|
43 |
edges: list[WorkspaceEdge] = dataclasses.field(default_factory=list)
|
44 |
|
45 |
|
46 |
-
def execute(ws: Workspace):
|
47 |
if ws.env in ops.EXECUTORS:
|
48 |
-
ops.EXECUTORS[ws.env](ws)
|
49 |
|
50 |
|
51 |
def save(ws: Workspace, path: str):
|
|
|
43 |
edges: list[WorkspaceEdge] = dataclasses.field(default_factory=list)
|
44 |
|
45 |
|
46 |
+
async def execute(ws: Workspace):
|
47 |
if ws.env in ops.EXECUTORS:
|
48 |
+
await ops.EXECUTORS[ws.env](ws)
|
49 |
|
50 |
|
51 |
def save(ws: Workspace, path: str):
|
web/src/NodeWithTableView.svelte
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { type NodeProps } from '@xyflow/svelte';
|
3 |
import LynxKiteNode from './LynxKiteNode.svelte';
|
4 |
import Table from './Table.svelte';
|
5 |
import SvelteMarkdown from 'svelte-markdown'
|
6 |
type $$Props = NodeProps;
|
7 |
export let data: $$Props['data'];
|
|
|
|
|
8 |
const open = {};
|
9 |
-
$: single =
|
10 |
function toMD(v) {
|
11 |
if (typeof v === 'string') {
|
12 |
return v;
|
@@ -19,8 +21,8 @@
|
|
19 |
</script>
|
20 |
|
21 |
<LynxKiteNode {...$$props}>
|
22 |
-
{#if
|
23 |
-
{#each Object.entries(
|
24 |
{#if !single}<div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>{/if}
|
25 |
{#if single || open[name]}
|
26 |
{#if df.data.length > 1}
|
@@ -35,7 +37,7 @@
|
|
35 |
{/if}
|
36 |
{/if}
|
37 |
{/each}
|
38 |
-
{#each Object.entries(
|
39 |
<div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>
|
40 |
{#if open[name]}
|
41 |
<pre>{o}</pre>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { useNodes, type NodeProps } from '@xyflow/svelte';
|
3 |
import LynxKiteNode from './LynxKiteNode.svelte';
|
4 |
import Table from './Table.svelte';
|
5 |
import SvelteMarkdown from 'svelte-markdown'
|
6 |
type $$Props = NodeProps;
|
7 |
export let data: $$Props['data'];
|
8 |
+
const nodes = useNodes(); // We don't properly get updates to "data". This is a hack.
|
9 |
+
$: D = $nodes && data;
|
10 |
const open = {};
|
11 |
+
$: single = D.display?.value?.dataframes && Object.keys(D.display.value.dataframes).length === 1;
|
12 |
function toMD(v) {
|
13 |
if (typeof v === 'string') {
|
14 |
return v;
|
|
|
21 |
</script>
|
22 |
|
23 |
<LynxKiteNode {...$$props}>
|
24 |
+
{#if D?.display?.value}
|
25 |
+
{#each Object.entries(D.display.value.dataframes || {}) as [name, df]}
|
26 |
{#if !single}<div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>{/if}
|
27 |
{#if single || open[name]}
|
28 |
{#if df.data.length > 1}
|
|
|
37 |
{/if}
|
38 |
{/if}
|
39 |
{/each}
|
40 |
+
{#each Object.entries(D.display.value.others || {}) as [name, o]}
|
41 |
<div class="df-head" on:click={() => open[name] = !open[name]}>{name}</div>
|
42 |
{#if open[name]}
|
43 |
<pre>{o}</pre>
|