@gradio/core 1.0.0-dev.0 → 1.0.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/src/Blocks.svelte +518 -1001
- package/dist/src/Blocks.svelte.d.ts +31 -45
- package/dist/src/Embed.svelte +82 -55
- package/dist/src/Embed.svelte.d.ts +39 -30
- package/dist/src/Login.svelte +33 -29
- package/dist/src/Login.svelte.d.ts +21 -19
- package/dist/src/MountComponents.svelte +19 -25
- package/dist/src/MountComponents.svelte.d.ts +5 -28
- package/dist/src/{init.d.ts → _init.d.ts} +5 -4
- package/dist/src/{init.js → _init.js} +31 -108
- package/dist/src/api_docs/ApiBanner.svelte +12 -8
- package/dist/src/api_docs/ApiBanner.svelte.d.ts +22 -20
- package/dist/src/api_docs/ApiDocs.svelte +337 -245
- package/dist/src/api_docs/ApiDocs.svelte.d.ts +26 -24
- package/dist/src/api_docs/ApiRecorder.svelte +9 -3
- package/dist/src/api_docs/ApiRecorder.svelte.d.ts +19 -17
- package/dist/src/api_docs/CodeSnippet.svelte +60 -30
- package/dist/src/api_docs/CodeSnippet.svelte.d.ts +27 -24
- package/dist/src/api_docs/CopyButton.svelte +69 -13
- package/dist/src/api_docs/CopyButton.svelte.d.ts +18 -16
- package/dist/src/api_docs/CopyMarkdown.svelte +734 -0
- package/dist/src/api_docs/CopyMarkdown.svelte.d.ts +37 -0
- package/dist/src/api_docs/EndpointDetail.svelte +8 -6
- package/dist/src/api_docs/EndpointDetail.svelte.d.ts +20 -18
- package/dist/src/api_docs/IconArrowUpRight.svelte +34 -0
- package/dist/src/api_docs/IconArrowUpRight.svelte.d.ts +20 -0
- package/dist/src/api_docs/IconCaret.svelte +39 -0
- package/dist/src/api_docs/IconCaret.svelte.d.ts +20 -0
- package/dist/src/api_docs/IconHuggingChat.svelte +62 -0
- package/dist/src/api_docs/IconHuggingChat.svelte.d.ts +20 -0
- package/dist/src/api_docs/InputPayload.svelte +17 -11
- package/dist/src/api_docs/InputPayload.svelte.d.ts +25 -23
- package/dist/src/api_docs/InstallSnippet.svelte +9 -6
- package/dist/src/api_docs/InstallSnippet.svelte.d.ts +18 -16
- package/dist/src/api_docs/MCPSnippet.svelte +119 -99
- package/dist/src/api_docs/MCPSnippet.svelte.d.ts +59 -58
- package/dist/src/api_docs/NoApi.svelte +7 -4
- package/dist/src/api_docs/NoApi.svelte.d.ts +20 -18
- package/dist/src/api_docs/ParametersSnippet.svelte +8 -6
- package/dist/src/api_docs/ParametersSnippet.svelte.d.ts +21 -19
- package/dist/src/api_docs/RecordingSnippet.svelte +124 -110
- package/dist/src/api_docs/RecordingSnippet.svelte.d.ts +24 -22
- package/dist/src/api_docs/ResponseSnippet.svelte +7 -5
- package/dist/src/api_docs/ResponseSnippet.svelte.d.ts +21 -19
- package/dist/src/api_docs/Settings.svelte +73 -62
- package/dist/src/api_docs/Settings.svelte.d.ts +25 -23
- package/dist/src/api_docs/SettingsBanner.svelte +11 -8
- package/dist/src/api_docs/SettingsBanner.svelte.d.ts +20 -18
- package/dist/src/api_docs/TryButton.svelte +5 -3
- package/dist/src/api_docs/TryButton.svelte.d.ts +19 -17
- package/dist/src/api_docs/img/IconCheck.svelte +33 -0
- package/dist/src/api_docs/img/IconCheck.svelte.d.ts +26 -0
- package/dist/src/api_docs/img/IconCopy.svelte +40 -0
- package/dist/src/api_docs/img/IconCopy.svelte.d.ts +26 -0
- package/dist/src/api_docs/img/clear.svelte.d.ts +22 -21
- package/dist/src/dependency.d.ts +142 -0
- package/dist/src/dependency.js +653 -0
- package/dist/src/init.svelte.d.ts +78 -0
- package/dist/src/init.svelte.js +469 -0
- package/dist/src/init_utils.d.ts +32 -0
- package/dist/src/init_utils.js +73 -0
- package/dist/src/lang/en.json +10 -1
- package/dist/src/lang/get_lang_names.js +0 -3
- package/dist/src/lang/ru.json +10 -1
- package/dist/src/stores.d.ts +0 -21
- package/dist/src/stories/I18nMultiLanguageTestComponent.svelte +5 -3
- package/dist/src/stories/I18nMultiLanguageTestComponent.svelte.d.ts +16 -14
- package/dist/src/stories/I18nTestSetup.svelte +14 -10
- package/dist/src/stories/I18nTestSetup.svelte.d.ts +18 -16
- package/dist/src/types.d.ts +30 -26
- package/index.ts +1 -1
- package/package.json +59 -59
- package/src/Blocks.svelte +344 -1063
- package/src/MountComponents.svelte +17 -27
- package/src/{init.ts → _init.ts} +49 -126
- package/src/api_docs/ApiDocs.svelte +65 -60
- package/src/api_docs/ApiRecorder.svelte +3 -0
- package/src/api_docs/CodeSnippet.svelte +20 -5
- package/src/api_docs/CopyButton.svelte +61 -7
- package/src/api_docs/CopyMarkdown.svelte +734 -0
- package/src/api_docs/IconArrowUpRight.svelte +34 -0
- package/src/api_docs/IconCaret.svelte +39 -0
- package/src/api_docs/IconHuggingChat.svelte +62 -0
- package/src/api_docs/MCPSnippet.svelte +24 -46
- package/src/api_docs/ParametersSnippet.svelte +1 -1
- package/src/api_docs/ResponseSnippet.svelte +1 -1
- package/src/api_docs/Settings.svelte +11 -11
- package/src/api_docs/img/IconCheck.svelte +33 -0
- package/src/api_docs/img/IconCopy.svelte +40 -0
- package/src/dependency.ts +880 -0
- package/src/init.svelte.ts +717 -0
- package/src/init_utils.ts +99 -0
- package/src/lang/en.json +10 -1
- package/src/lang/get_lang_names.js +0 -3
- package/src/lang/ru.json +10 -1
- package/src/stores.ts +22 -22
- package/src/types.ts +54 -43
- package/dist/src/Render.svelte +0 -105
- package/dist/src/Render.svelte.d.ts +0 -31
- package/dist/src/RenderComponent.svelte +0 -72
- package/dist/src/RenderComponent.svelte.d.ts +0 -33
- package/src/Render.svelte +0 -126
- package/src/RenderComponent.svelte +0 -91
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { AsyncFunction } from "./init_utils";
|
|
2
|
+
import { Client } from "@gradio/client";
|
|
3
|
+
import { LoadingStatus } from "@gradio/statustracker";
|
|
4
|
+
const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
|
|
5
|
+
const NOVALUE = Symbol("NOVALUE");
|
|
6
|
+
/**
|
|
7
|
+
* A dependency as used by the frontend
|
|
8
|
+
* This class represents a discrete dependency that can be triggered by an event
|
|
9
|
+
* It is responsible calling the appropriate functions and reporting back results
|
|
10
|
+
*/
|
|
11
|
+
export class Dependency {
|
|
12
|
+
id;
|
|
13
|
+
inputs;
|
|
14
|
+
outputs;
|
|
15
|
+
cancels;
|
|
16
|
+
pending = false;
|
|
17
|
+
trigger_modes;
|
|
18
|
+
event_args = {};
|
|
19
|
+
targets = [];
|
|
20
|
+
connection_type;
|
|
21
|
+
// if this dependency has any then, success or failure triggers
|
|
22
|
+
triggers = [];
|
|
23
|
+
// the id of the original event_id that caused this dependency to run
|
|
24
|
+
// in the case of chained events, it would be the id of the initial trigger
|
|
25
|
+
original_trigger_id = null;
|
|
26
|
+
show_progress_on = null;
|
|
27
|
+
functions;
|
|
28
|
+
constructor(dep_config) {
|
|
29
|
+
this.id = dep_config.id;
|
|
30
|
+
this.original_trigger_id = dep_config.id;
|
|
31
|
+
this.inputs = dep_config.inputs;
|
|
32
|
+
this.outputs = dep_config.outputs;
|
|
33
|
+
this.connection_type = dep_config.connection;
|
|
34
|
+
this.functions = {
|
|
35
|
+
frontend: dep_config.js
|
|
36
|
+
? process_frontend_fn(dep_config.js, dep_config.backend_fn, dep_config.inputs.length, dep_config.outputs.length)
|
|
37
|
+
: undefined,
|
|
38
|
+
backend: dep_config.backend_fn,
|
|
39
|
+
backend_js: dep_config.js_implementation
|
|
40
|
+
? new AsyncFunction(`let result = await (${dep_config.js_implementation})(...arguments);
|
|
41
|
+
return (!Array.isArray(result)) ? [result] : result;`)
|
|
42
|
+
: undefined
|
|
43
|
+
};
|
|
44
|
+
this.targets = dep_config.targets;
|
|
45
|
+
this.cancels = dep_config.cancels;
|
|
46
|
+
this.trigger_modes = dep_config.trigger_mode;
|
|
47
|
+
this.show_progress_on = dep_config.show_progress_on || null;
|
|
48
|
+
for (let i = 0; i < dep_config.event_specific_args?.length || 0; i++) {
|
|
49
|
+
const key = dep_config.event_specific_args[i];
|
|
50
|
+
this.event_args[key] = dep_config[key] ?? null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async run(client, data_payload, event_data, target_id) {
|
|
54
|
+
let _data_payload = data_payload;
|
|
55
|
+
// if the function is backend_js, then it's the entire event
|
|
56
|
+
// no need to chain frontend and backend
|
|
57
|
+
if (this.functions.backend_js) {
|
|
58
|
+
const data = await this.functions.backend_js(..._data_payload);
|
|
59
|
+
return { type: "data", data };
|
|
60
|
+
}
|
|
61
|
+
// If it has a js implementation, the correct behavior
|
|
62
|
+
// is to run that and pass the output to the backend
|
|
63
|
+
if (this.functions.frontend) {
|
|
64
|
+
_data_payload = await this.functions.frontend(data_payload);
|
|
65
|
+
}
|
|
66
|
+
if (this.functions.backend) {
|
|
67
|
+
return {
|
|
68
|
+
type: "submit",
|
|
69
|
+
data: client.submit(this.id, _data_payload, event_data, target_id)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
else if (this.functions.frontend) {
|
|
73
|
+
return { type: "data", data: _data_payload };
|
|
74
|
+
}
|
|
75
|
+
return { type: "void", data: null };
|
|
76
|
+
}
|
|
77
|
+
add_trigger(dep_id, condition) {
|
|
78
|
+
this.triggers.push([dep_id, condition]);
|
|
79
|
+
}
|
|
80
|
+
get_triggers() {
|
|
81
|
+
return {
|
|
82
|
+
success: this.triggers
|
|
83
|
+
.filter(([, condition]) => condition === "success")
|
|
84
|
+
.map(([id]) => id),
|
|
85
|
+
failure: this.triggers
|
|
86
|
+
.filter(([, condition]) => condition === "failure")
|
|
87
|
+
.map(([id]) => id),
|
|
88
|
+
all: this.triggers
|
|
89
|
+
.filter(([, condition]) => condition === "all")
|
|
90
|
+
.map(([id]) => id)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Manages all dependencies for an app acting as a bridge between app state and Dependencies
|
|
96
|
+
* Responsible for registering dependencies and dispatching events to them
|
|
97
|
+
* It is also responsible for orchestrating dependencies based on the follwing:
|
|
98
|
+
* - Cancelling dependencies
|
|
99
|
+
* - Ensuring individual dependencies respect `trigger_mode`
|
|
100
|
+
* - Managing then, success and failure events
|
|
101
|
+
* - Ensuring that dependencies bound to the same id are treated a single unit
|
|
102
|
+
* - updating loading states
|
|
103
|
+
* - updating component states
|
|
104
|
+
*/
|
|
105
|
+
export class DependencyManager {
|
|
106
|
+
dependencies_by_fn = new Map();
|
|
107
|
+
dependencies_by_event = new Map();
|
|
108
|
+
render_id_deps = new Map();
|
|
109
|
+
submissions = new Map();
|
|
110
|
+
client;
|
|
111
|
+
queue = new Set();
|
|
112
|
+
add_to_api_calls;
|
|
113
|
+
update_state_cb;
|
|
114
|
+
get_state_cb;
|
|
115
|
+
rerender_cb;
|
|
116
|
+
log_cb;
|
|
117
|
+
loading_stati = new LoadingStatus();
|
|
118
|
+
constructor(dependencies, client, update_state_cb, get_state_cb, rerender_cb, log_cb, add_to_api_calls) {
|
|
119
|
+
this.add_to_api_calls = add_to_api_calls;
|
|
120
|
+
this.client = client;
|
|
121
|
+
this.log_cb = log_cb;
|
|
122
|
+
// this.update_state_cb = update_state_cb;
|
|
123
|
+
// this.get_state_cb = get_state_cb;
|
|
124
|
+
// this.rerender_cb = rerender_cb;
|
|
125
|
+
this.reload(dependencies, update_state_cb, get_state_cb, rerender_cb);
|
|
126
|
+
}
|
|
127
|
+
reload(dependencies, update_state, get_state, rerender, client) {
|
|
128
|
+
const { by_id, by_event } = this.create(dependencies);
|
|
129
|
+
this.dependencies_by_event = by_event;
|
|
130
|
+
this.dependencies_by_fn = by_id;
|
|
131
|
+
for (const [dep_id, dep] of this.dependencies_by_fn) {
|
|
132
|
+
for (const [output_id] of dep.targets) {
|
|
133
|
+
this.set_event_args(output_id, dep.event_args);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.client = client;
|
|
137
|
+
this.update_state_cb = update_state;
|
|
138
|
+
this.get_state_cb = get_state;
|
|
139
|
+
this.rerender_cb = rerender;
|
|
140
|
+
this.register_loading_stati(by_id);
|
|
141
|
+
}
|
|
142
|
+
register_loading_stati(deps) {
|
|
143
|
+
for (const [_, dep] of deps) {
|
|
144
|
+
this.loading_stati.register(dep.id, dep.show_progress_on || dep.outputs, dep.inputs);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
clear_loading_status(component_id) {
|
|
148
|
+
this.loading_stati.clear(component_id);
|
|
149
|
+
}
|
|
150
|
+
async update_loading_stati_state() {
|
|
151
|
+
for (const [component_id, loading_status] of Object.entries(this.loading_stati.current)) {
|
|
152
|
+
this.update_state_cb(Number(component_id), {
|
|
153
|
+
loading_status: loading_status
|
|
154
|
+
}, false);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
dispatch_state_change_events(result) {
|
|
158
|
+
if (result.changed_state_ids) {
|
|
159
|
+
for (const changed_id of result.changed_state_ids) {
|
|
160
|
+
const change_dep = this.dependencies_by_event.get("change-" + changed_id);
|
|
161
|
+
change_dep?.forEach((dep) => {
|
|
162
|
+
this.dispatch({
|
|
163
|
+
type: "fn",
|
|
164
|
+
fn_index: dep.id,
|
|
165
|
+
target_id: changed_id,
|
|
166
|
+
event_data: null
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Dispatches an event to the appropriate dependency
|
|
173
|
+
* @param event_name the name of the event
|
|
174
|
+
* @param target_id the id of the component that triggered the event
|
|
175
|
+
* @param event_data any additional data to pass to the dependency
|
|
176
|
+
* @returns a value if there is no backend fn, a 'submission' if there is a backend fn, or null if there is no dependency
|
|
177
|
+
*/
|
|
178
|
+
async dispatch(event_meta) {
|
|
179
|
+
let deps;
|
|
180
|
+
if (event_meta.type === "fn") {
|
|
181
|
+
const dep = this.dependencies_by_fn.get(event_meta.fn_index);
|
|
182
|
+
if (dep)
|
|
183
|
+
deps = [dep];
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
deps = this.dependencies_by_event.get(`${event_meta.event_name}-${event_meta.target_id}`);
|
|
187
|
+
}
|
|
188
|
+
for (let i = 0; i < (deps?.length || 0); i++) {
|
|
189
|
+
const dep = deps ? deps[i] : undefined;
|
|
190
|
+
if (dep) {
|
|
191
|
+
this.cancel(dep.cancels);
|
|
192
|
+
const dispatch_status = should_dispatch(dep.trigger_modes, this.submissions.has(dep.id));
|
|
193
|
+
if (dispatch_status === "skip") {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
else if (dispatch_status === "defer") {
|
|
197
|
+
this.queue.add(dep.id);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// No loading status for js-only deps
|
|
201
|
+
if (dep.functions.backend) {
|
|
202
|
+
this.loading_stati.update({
|
|
203
|
+
status: "pending",
|
|
204
|
+
fn_index: dep.id,
|
|
205
|
+
stream_state: null
|
|
206
|
+
});
|
|
207
|
+
this.update_loading_stati_state();
|
|
208
|
+
}
|
|
209
|
+
const data_payload = await this.gather_state(dep.inputs);
|
|
210
|
+
const unset_args = await Promise.all(dep.targets.map(([output_id]) => this.set_event_args(output_id, dep.event_args)));
|
|
211
|
+
const { success, failure, all } = dep.get_triggers();
|
|
212
|
+
try {
|
|
213
|
+
let target_id = null;
|
|
214
|
+
if (event_meta.target_id !== undefined ||
|
|
215
|
+
event_meta.type === "event") {
|
|
216
|
+
target_id = event_meta.target_id || null;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
target_id = dep.original_trigger_id;
|
|
220
|
+
}
|
|
221
|
+
if (dep.connection_type === "stream" &&
|
|
222
|
+
this.submissions.has(dep.id)) {
|
|
223
|
+
const submission = this.submissions.get(dep.id);
|
|
224
|
+
let payload = {
|
|
225
|
+
fn_index: dep.id,
|
|
226
|
+
data: data_payload,
|
|
227
|
+
event_data: event_meta.event_data
|
|
228
|
+
};
|
|
229
|
+
submission.send_chunk(payload);
|
|
230
|
+
unset_args.forEach((fn) => fn());
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
this.add_to_api_calls({
|
|
234
|
+
fn_index: dep.id,
|
|
235
|
+
data: data_payload,
|
|
236
|
+
event_data: event_meta.event_data,
|
|
237
|
+
trigger_id: target_id
|
|
238
|
+
});
|
|
239
|
+
const dep_submission = await dep.run(this.client, data_payload, event_meta.event_data, target_id);
|
|
240
|
+
if (dep_submission.type === "void") {
|
|
241
|
+
unset_args.forEach((fn) => fn());
|
|
242
|
+
}
|
|
243
|
+
else if (dep_submission.type === "data") {
|
|
244
|
+
this.handle_data(dep.outputs, dep_submission.data);
|
|
245
|
+
unset_args.forEach((fn) => fn());
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
let stream_state = null;
|
|
249
|
+
if (dep.connection_type === "stream" &&
|
|
250
|
+
!this.submissions.has(dep.id)) {
|
|
251
|
+
stream_state = "waiting";
|
|
252
|
+
}
|
|
253
|
+
this.submissions.set(dep.id, dep_submission.data);
|
|
254
|
+
let index = 0;
|
|
255
|
+
// fn for this?
|
|
256
|
+
submit_loop: for await (const result of dep_submission.data) {
|
|
257
|
+
if (index === 0) {
|
|
258
|
+
// Clear out previously set validation errors
|
|
259
|
+
dep.inputs.forEach((input_id) => {
|
|
260
|
+
this.update_state_cb(input_id, {
|
|
261
|
+
loading_status: {
|
|
262
|
+
validation_error: null
|
|
263
|
+
}
|
|
264
|
+
}, false);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
index += 1;
|
|
268
|
+
if (result === null)
|
|
269
|
+
continue;
|
|
270
|
+
if (result.type === "data") {
|
|
271
|
+
this.handle_data(dep.outputs, result.data);
|
|
272
|
+
}
|
|
273
|
+
if (result.type === "status") {
|
|
274
|
+
if (result.original_msg === "process_starts" &&
|
|
275
|
+
dep.connection_type === "stream") {
|
|
276
|
+
stream_state = "open";
|
|
277
|
+
}
|
|
278
|
+
const { fn_index, ...status } = result;
|
|
279
|
+
// handle status updates here
|
|
280
|
+
if (result.stage === "complete") {
|
|
281
|
+
stream_state = "closed";
|
|
282
|
+
success.forEach((dep_id) => {
|
|
283
|
+
this.dispatch({
|
|
284
|
+
type: "fn",
|
|
285
|
+
fn_index: dep_id,
|
|
286
|
+
event_data: null,
|
|
287
|
+
target_id: target_id
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
this.dispatch_state_change_events(result);
|
|
291
|
+
// @ts-ignore
|
|
292
|
+
this.loading_stati.update({
|
|
293
|
+
...status,
|
|
294
|
+
status: status.stage,
|
|
295
|
+
fn_index: dep.id,
|
|
296
|
+
stream_state
|
|
297
|
+
});
|
|
298
|
+
this.update_loading_stati_state();
|
|
299
|
+
break submit_loop;
|
|
300
|
+
}
|
|
301
|
+
else if (result.stage === "generating") {
|
|
302
|
+
this.dispatch_state_change_events(result);
|
|
303
|
+
// @ts-ignore
|
|
304
|
+
this.loading_stati.update({
|
|
305
|
+
...status,
|
|
306
|
+
status: status.stage,
|
|
307
|
+
fn_index: dep.id,
|
|
308
|
+
stream_state
|
|
309
|
+
});
|
|
310
|
+
this.update_loading_stati_state();
|
|
311
|
+
}
|
|
312
|
+
else if (result.stage === "error") {
|
|
313
|
+
if (Array.isArray(result?.message)) {
|
|
314
|
+
result.message.forEach((m, i) => {
|
|
315
|
+
this.update_state_cb(dep.inputs[i], {
|
|
316
|
+
loading_status: {
|
|
317
|
+
validation_error: !m.is_valid ? m.message : null,
|
|
318
|
+
show_validation_error: true
|
|
319
|
+
}
|
|
320
|
+
}, false);
|
|
321
|
+
});
|
|
322
|
+
// Manually set the output statuses to null
|
|
323
|
+
// Doing this in update_loading_stati_state would
|
|
324
|
+
// validation errors set above
|
|
325
|
+
// For example, if the input component is an output component (chatinterface)
|
|
326
|
+
dep.outputs.forEach((output_id) => {
|
|
327
|
+
if (dep.inputs.includes(output_id))
|
|
328
|
+
return;
|
|
329
|
+
this.update_state_cb(output_id, {
|
|
330
|
+
loading_status: {
|
|
331
|
+
status: null
|
|
332
|
+
}
|
|
333
|
+
}, false);
|
|
334
|
+
});
|
|
335
|
+
unset_args.forEach((fn) => fn());
|
|
336
|
+
this.submissions.delete(dep.id);
|
|
337
|
+
if (this.queue.has(dep.id)) {
|
|
338
|
+
this.queue.delete(dep.id);
|
|
339
|
+
this.dispatch(event_meta);
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const _message = result?.message?.replace(MESSAGE_QUOTE_RE, (_, b) => b);
|
|
344
|
+
this.log_cb(result._title ?? "Error", _message, fn_index, "error", status.duration, status.visible);
|
|
345
|
+
throw new Error("Dependency function failed");
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// @ts-ignore
|
|
349
|
+
this.loading_stati.update({
|
|
350
|
+
...status,
|
|
351
|
+
status: status.stage,
|
|
352
|
+
fn_index: dep.id,
|
|
353
|
+
stream_state
|
|
354
|
+
});
|
|
355
|
+
this.update_loading_stati_state();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (result.type === "render") {
|
|
359
|
+
const { layout, components, render_id, dependencies } = result.data;
|
|
360
|
+
this.rerender_cb(components, layout);
|
|
361
|
+
// update dependencies
|
|
362
|
+
const { by_id, by_event } = this.create(dependencies);
|
|
363
|
+
this.register_loading_stati(by_id);
|
|
364
|
+
by_id.forEach((dep) => this.dependencies_by_fn.set(dep.id, dep));
|
|
365
|
+
by_event.forEach((dep, key) => this.dependencies_by_event.set(key, dep));
|
|
366
|
+
const current_deps = this.render_id_deps.get(render_id);
|
|
367
|
+
if (current_deps) {
|
|
368
|
+
current_deps.forEach((old_dep_id) => {
|
|
369
|
+
if (!by_id.has(old_dep_id)) {
|
|
370
|
+
this.dependencies_by_fn.delete(old_dep_id);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
this.render_id_deps.set(render_id, new Set(Array.from(by_id.keys())));
|
|
375
|
+
this.register_loading_stati(by_id);
|
|
376
|
+
break submit_loop;
|
|
377
|
+
}
|
|
378
|
+
if (result.type === "log") {
|
|
379
|
+
this.handle_log(result);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
all.forEach((dep_id) => {
|
|
383
|
+
this.dispatch({
|
|
384
|
+
type: "fn",
|
|
385
|
+
fn_index: dep_id,
|
|
386
|
+
event_data: null,
|
|
387
|
+
target_id: target_id
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
unset_args.forEach((fn) => fn());
|
|
391
|
+
this.submissions.delete(dep.id);
|
|
392
|
+
if (this.queue.has(dep.id)) {
|
|
393
|
+
this.queue.delete(dep.id);
|
|
394
|
+
this.dispatch(event_meta);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
this.loading_stati.update({
|
|
400
|
+
status: "error",
|
|
401
|
+
fn_index: dep.id,
|
|
402
|
+
eta: 0,
|
|
403
|
+
queue: false,
|
|
404
|
+
stream_state: null
|
|
405
|
+
});
|
|
406
|
+
this.update_loading_stati_state();
|
|
407
|
+
this.submissions.delete(dep.id);
|
|
408
|
+
failure.forEach((dep_id) => {
|
|
409
|
+
this.dispatch({
|
|
410
|
+
type: "fn",
|
|
411
|
+
fn_index: dep_id,
|
|
412
|
+
event_data: null
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Creates a map of dependencies for easy lookup
|
|
422
|
+
*
|
|
423
|
+
* @param dependencies the list of dependencies from the backend
|
|
424
|
+
* @returns a map of dependencies keyed by `${event_name}-${target_id}`
|
|
425
|
+
* */
|
|
426
|
+
create(dependencies) {
|
|
427
|
+
const _deps_by_id = new Map();
|
|
428
|
+
const _deps_by_event = new Map();
|
|
429
|
+
const then_triggers = [];
|
|
430
|
+
for (const dep_config of dependencies) {
|
|
431
|
+
const dependency = new Dependency(dep_config);
|
|
432
|
+
for (const [target_id, event_name] of dep_config.targets) {
|
|
433
|
+
// if the key is already present, add it to the list. Otherwise, create a new element with the list
|
|
434
|
+
if (!_deps_by_event.has(`${event_name}-${target_id}`)) {
|
|
435
|
+
_deps_by_event.set(`${event_name}-${target_id}`, []);
|
|
436
|
+
}
|
|
437
|
+
_deps_by_event.get(`${event_name}-${target_id}`)?.push(dependency);
|
|
438
|
+
}
|
|
439
|
+
_deps_by_id.set(dep_config.id, dependency);
|
|
440
|
+
if (dep_config.trigger_after !== undefined) {
|
|
441
|
+
const then_mode = dep_config.trigger_only_on_failure
|
|
442
|
+
? "failure"
|
|
443
|
+
: dep_config.trigger_only_on_success
|
|
444
|
+
? "success"
|
|
445
|
+
: "all";
|
|
446
|
+
then_triggers.push([
|
|
447
|
+
dep_config.id,
|
|
448
|
+
dep_config.trigger_after,
|
|
449
|
+
then_mode
|
|
450
|
+
]);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
for (const [dep_id, trigger_after, condition] of then_triggers) {
|
|
454
|
+
const dependency = _deps_by_id.get(trigger_after);
|
|
455
|
+
if (dependency) {
|
|
456
|
+
dependency.add_trigger(dep_id, condition);
|
|
457
|
+
dependency.original_trigger_id = walk_after_to_original(dependencies, trigger_after);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { by_id: _deps_by_id, by_event: _deps_by_event };
|
|
461
|
+
}
|
|
462
|
+
handle_log(msg) {
|
|
463
|
+
const { title, log, fn_index, level, duration, visible } = msg;
|
|
464
|
+
this.log_cb(title, log, fn_index, level, duration, visible);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Updates the state of the outputs based on the data received from the dependency
|
|
468
|
+
*
|
|
469
|
+
* @param outputs the ids of the output components
|
|
470
|
+
* @param data the data to update the components with
|
|
471
|
+
* */
|
|
472
|
+
async handle_data(outputs, data) {
|
|
473
|
+
outputs.forEach(async (output_id, i) => {
|
|
474
|
+
const _data = data[i] === undefined ? NOVALUE : data[i];
|
|
475
|
+
if (_data === NOVALUE)
|
|
476
|
+
return;
|
|
477
|
+
if (is_prop_update(_data)) {
|
|
478
|
+
let pending_visibility_update = false;
|
|
479
|
+
let pending_visibility_value = null;
|
|
480
|
+
for (const [update_key, update_value] of Object.entries(_data)) {
|
|
481
|
+
if (update_key === "__type__")
|
|
482
|
+
continue;
|
|
483
|
+
if (update_key === "visible") {
|
|
484
|
+
pending_visibility_update = true;
|
|
485
|
+
pending_visibility_value = update_value;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
await this.update_state_cb(outputs[i], {
|
|
489
|
+
[update_key]: update_value
|
|
490
|
+
}, false);
|
|
491
|
+
}
|
|
492
|
+
if (pending_visibility_update) {
|
|
493
|
+
await this.update_state_cb(outputs[i], {
|
|
494
|
+
visible: pending_visibility_value
|
|
495
|
+
}, true);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
await this.update_state_cb(output_id, { value: _data }, false);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Gathers the current state of the inputs
|
|
505
|
+
*
|
|
506
|
+
* @param ids the ids of the components to gather state from
|
|
507
|
+
* @returns an array of the current state of the components, in the same order as the ids
|
|
508
|
+
*/
|
|
509
|
+
async gather_state(ids) {
|
|
510
|
+
return (await Promise.all(ids.map((id) => this.get_state_cb(id)))).map((state) => {
|
|
511
|
+
return state?.value ?? null;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/** Sets the event arguments for a specific component
|
|
515
|
+
*
|
|
516
|
+
* @param id the id of the component to set the event arguments for
|
|
517
|
+
* @param args the event arguments to set
|
|
518
|
+
* @returns a function that can be called to reset the event arguments to their previous values
|
|
519
|
+
*/
|
|
520
|
+
async set_event_args(id, args) {
|
|
521
|
+
let current_args = {};
|
|
522
|
+
const current_state = await this.get_state_cb(id);
|
|
523
|
+
for (const [key] of Object.entries(args)) {
|
|
524
|
+
current_args[key] = current_state?.[key] ?? null;
|
|
525
|
+
}
|
|
526
|
+
if (Object.keys(args).length === 0) {
|
|
527
|
+
return () => {
|
|
528
|
+
// do nothing
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
await this.update_state_cb(id, args, false);
|
|
532
|
+
return () => {
|
|
533
|
+
this.update_state_cb(id, current_args, false);
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
async cancel(ids) {
|
|
537
|
+
if (!ids)
|
|
538
|
+
return;
|
|
539
|
+
for (const id of ids) {
|
|
540
|
+
const submission = this.submissions.get(id);
|
|
541
|
+
if (submission) {
|
|
542
|
+
await submission.cancel();
|
|
543
|
+
this.submissions.delete(id);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
dispatch_load_events() {
|
|
548
|
+
this.dependencies_by_fn.forEach((dep) => {
|
|
549
|
+
dep.targets.forEach(([target_id, event_name]) => {
|
|
550
|
+
if (event_name === "load") {
|
|
551
|
+
this.dispatch({
|
|
552
|
+
type: "fn",
|
|
553
|
+
fn_index: dep.id,
|
|
554
|
+
event_data: null,
|
|
555
|
+
target_id: target_id
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
get_fns_from_targets(target_id) {
|
|
562
|
+
const fn_indices = [];
|
|
563
|
+
this.dependencies_by_event.forEach((deps, key) => {
|
|
564
|
+
const [, dep_target_id] = key.split("-");
|
|
565
|
+
if (Number(dep_target_id) === target_id) {
|
|
566
|
+
deps.forEach((dep) => {
|
|
567
|
+
fn_indices.push(dep.id);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
return fn_indices;
|
|
572
|
+
}
|
|
573
|
+
close_stream(id) {
|
|
574
|
+
const fn_ids = this.get_fns_from_targets(id);
|
|
575
|
+
for (const fn_id of fn_ids) {
|
|
576
|
+
const submission = this.submissions.get(fn_id);
|
|
577
|
+
if (submission) {
|
|
578
|
+
submission.close_stream();
|
|
579
|
+
this.submissions.delete(fn_id);
|
|
580
|
+
}
|
|
581
|
+
this.loading_stati.update({
|
|
582
|
+
status: "complete",
|
|
583
|
+
fn_index: fn_id,
|
|
584
|
+
eta: 0,
|
|
585
|
+
queue: false,
|
|
586
|
+
stream_state: "closed"
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
this.update_loading_stati_state();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function is_prop_update(payload) {
|
|
593
|
+
return (typeof payload === "object" &&
|
|
594
|
+
payload !== null &&
|
|
595
|
+
"__type__" in payload &&
|
|
596
|
+
payload?.__type__ === "update");
|
|
597
|
+
}
|
|
598
|
+
function should_dispatch(mode, is_running) {
|
|
599
|
+
if (!is_running)
|
|
600
|
+
return "run";
|
|
601
|
+
if (mode === "always_last") {
|
|
602
|
+
return "defer";
|
|
603
|
+
}
|
|
604
|
+
else if (mode === "multiple") {
|
|
605
|
+
return "run";
|
|
606
|
+
}
|
|
607
|
+
else if (mode === "once") {
|
|
608
|
+
return "skip";
|
|
609
|
+
}
|
|
610
|
+
return "run";
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Takes a string of source code and returns a function that can be called with arguments
|
|
614
|
+
* @param source the source code
|
|
615
|
+
* @param backend_fn if there is also a backend function
|
|
616
|
+
* @param input_length the number of inputs
|
|
617
|
+
* @param output_length the number of outputs
|
|
618
|
+
* @returns The function, or null if the source code is invalid or missing
|
|
619
|
+
*/
|
|
620
|
+
export function process_frontend_fn(source, backend_fn, input_length, output_length) {
|
|
621
|
+
const wrap = backend_fn ? input_length === 1 : output_length === 1;
|
|
622
|
+
try {
|
|
623
|
+
return new AsyncFunction("__fn_args", ` let result = await (${source})(...__fn_args);
|
|
624
|
+
if (typeof result === "undefined") return [];
|
|
625
|
+
return (${wrap} && !Array.isArray(result)) ? [result] : result;`);
|
|
626
|
+
}
|
|
627
|
+
catch (e) {
|
|
628
|
+
throw e;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Walks the dependency graph to find the original trigger ID for a given dependency.
|
|
633
|
+
* @param dependency_map The map of all dependencies.
|
|
634
|
+
* @param dep_id The ID of the dependency to start from.
|
|
635
|
+
* @returns The ID of the original trigger dependency, or the input ID if not found.
|
|
636
|
+
*/
|
|
637
|
+
function walk_after_to_original(dependency_map, dep_id) {
|
|
638
|
+
// TODO: hoist this cache later so it is useful across multiple calls
|
|
639
|
+
let cache = new Map();
|
|
640
|
+
let current_id = dep_id;
|
|
641
|
+
let safety_counter = 0;
|
|
642
|
+
while (safety_counter < 100) {
|
|
643
|
+
const dep = cache.get(current_id) || dependency_map.find((d) => d.id === current_id);
|
|
644
|
+
if (!dep)
|
|
645
|
+
break;
|
|
646
|
+
cache.set(dep.id, dep);
|
|
647
|
+
if (dep.trigger_after === null || dep.trigger_after === undefined)
|
|
648
|
+
break;
|
|
649
|
+
current_id = dep.trigger_after;
|
|
650
|
+
safety_counter += 1;
|
|
651
|
+
}
|
|
652
|
+
return current_id;
|
|
653
|
+
}
|