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>
|