From 3349affdbeef8e471599c4d2c94698f111cf6942 Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 3 Jun 2024 16:08:42 +0200 Subject: [PATCH 01/13] send `null` for each `state` param in space api --- client/js/src/client.ts | 3 +- client/js/src/test/api_info.test.ts | 1 - client/js/src/types.ts | 53 +++++++++++++++++++++++++++-- client/js/src/utils/predict.ts | 8 ++--- client/js/src/utils/stream.ts | 4 ++- client/js/src/utils/submit.ts | 23 +++++++++++-- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/client/js/src/client.ts b/client/js/src/client.ts index 3ff5e7f6a736f..752e80d03d5a3 100644 --- a/client/js/src/client.ts +++ b/client/js/src/client.ts @@ -6,6 +6,7 @@ import type { DuplicateOptions, EndpointInfo, JsApiData, + PredictReturn, SpaceStatus, Status, SubmitReturn, @@ -114,7 +115,7 @@ export class Client { endpoint: string | number, data: unknown[] | Record, event_data?: unknown - ) => Promise; + ) => Promise; open_stream: () => Promise; private resolve_config: (endpoint: string) => Promise; private resolve_cookies: () => Promise; diff --git a/client/js/src/test/api_info.test.ts b/client/js/src/test/api_info.test.ts index bf6c413c5ef80..ad0a7538eaeef 100644 --- a/client/js/src/test/api_info.test.ts +++ b/client/js/src/test/api_info.test.ts @@ -16,7 +16,6 @@ import { initialise_server } from "./server"; import { transformed_api_info } from "./test_data"; const server = initialise_server(); -const IS_NODE = process.env.TEST_MODE === "node"; beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); diff --git a/client/js/src/types.ts b/client/js/src/types.ts index 244bad47e1d68..b04ac264831bd 100644 --- a/client/js/src/types.ts +++ b/client/js/src/types.ts @@ -1,6 +1,8 @@ // API Data Types import { hardware_types } from "./helpers/spaces"; +import type { SvelteComponent } from "svelte"; +import type { ComponentType } from "svelte"; export interface ApiData { label: string; @@ -62,7 +64,7 @@ export type PredictFunction = ( endpoint: string | number, data: unknown[] | Record, event_data?: unknown -) => Promise; +) => Promise; // Event and Submission Types @@ -90,6 +92,14 @@ export type SubmitReturn = { destroy: () => void; }; +export type PredictReturn = { + type: EventType; + time: Date; + data: unknown; + endpoint: string; + fn_index: number; +}; + // Space Status Types export type SpaceStatus = SpaceStatusNormal | SpaceStatusError; @@ -128,7 +138,7 @@ export interface Config { analytics_enabled: boolean; connect_heartbeat: boolean; auth_message: string; - components: any[]; + components: ComponentMeta[]; css: string | null; js: string | null; head: string | null; @@ -153,6 +163,45 @@ export interface Config { max_file_size?: number; } +// todo: DRY up types +export interface ComponentMeta { + type: string; + id: number; + has_modes: boolean; + props: SharedProps; + instance: SvelteComponent; + component: ComponentType; + documentation?: Documentation; + children?: ComponentMeta[]; + parent?: ComponentMeta; + value?: any; + component_class_id: string; + key: string | number | null; + rendered_in?: number; +} + +interface SharedProps { + elem_id?: string; + elem_classes?: string[]; + components?: string[]; + server_fns?: string[]; + interactive: boolean; + [key: string]: unknown; + root_url?: string; +} + +export interface Documentation { + type?: TypeDescription; + description?: TypeDescription; + example_data?: string; +} + +interface TypeDescription { + input_payload?: string; + response_object?: string; + payload?: string; +} + export interface Dependency { id: number; targets: [number, string][]; diff --git a/client/js/src/utils/predict.ts b/client/js/src/utils/predict.ts index 74d89560759bc..a4bf47aa916d5 100644 --- a/client/js/src/utils/predict.ts +++ b/client/js/src/utils/predict.ts @@ -1,11 +1,11 @@ import { Client } from "../client"; -import type { Dependency, SubmitReturn } from "../types"; +import type { Dependency, PredictReturn } from "../types"; export async function predict( this: Client, endpoint: string | number, data: unknown[] | Record -): Promise { +): Promise { let data_returned = false; let status_complete = false; let dependency: Dependency; @@ -38,7 +38,7 @@ export async function predict( // if complete message comes before data, resolve here if (status_complete) { app.destroy(); - resolve(d as SubmitReturn); + resolve(d as PredictReturn); } data_returned = true; result = d; @@ -50,7 +50,7 @@ export async function predict( // if complete message comes after data, resolve here if (data_returned) { app.destroy(); - resolve(result as SubmitReturn); + resolve(result as PredictReturn); } } }); diff --git a/client/js/src/utils/stream.ts b/client/js/src/utils/stream.ts index 02df1a968919d..9134d2eb880cb 100644 --- a/client/js/src/utils/stream.ts +++ b/client/js/src/utils/stream.ts @@ -51,7 +51,9 @@ export async function open_stream(this: Client): Promise { } else if (event_callbacks[event_id] && config) { if ( _data.msg === "process_completed" && - ["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol) + ["sse", "sse_v1", "sse_v2", "sse_v2.1", "sse_v3"].includes( + config.protocol + ) ) { unclosed_events.delete(event_id); if (unclosed_events.size === 0) { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index 80ca94b7831fa..f092d5dc21865 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -11,7 +11,8 @@ import type { EndpointInfo, ApiInfo, Config, - Dependency + Dependency, + ComponentMeta } from "../types"; import { skip_queue, post_message } from "../helpers/data"; @@ -191,10 +192,26 @@ export function submit( }); } + // Builds the payload for submitting data to the server, replacing state components with null + function build_payload( + resolved_payload: unknown[], + dependency: Dependency, + components: ComponentMeta[] + ): any[] { + return dependency.inputs.map((input_id: number, index: number) => { + const component = components.find((c: any) => c.id === input_id); + if (component?.type === "state") { + return null; + } + return resolved_payload[index]; + }); + } + this.handle_blob(config.root, resolved_data, endpoint_info).then( async (_payload) => { + let input_data = build_payload(_payload, dependency, config.components); payload = { - data: _payload || [], + data: input_data || [], event_data, fn_index, trigger_id @@ -676,7 +693,7 @@ export function submit( fn_index, time: new Date() }); - if (["sse_v2", "sse_v2.1"].includes(protocol)) { + if (["sse_v2", "sse_v2.1", "sse_v3"].includes(protocol)) { close_stream(stream_status, stream); stream_status.open = false; } From 777a49e696ad6a9ed02220e0ee1bb1c0dafa6605 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Mon, 3 Jun 2024 14:13:07 +0000 Subject: [PATCH 02/13] add changeset --- .changeset/young-poets-change.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/young-poets-change.md diff --git a/.changeset/young-poets-change.md b/.changeset/young-poets-change.md new file mode 100644 index 0000000000000..b7667a07d1642 --- /dev/null +++ b/.changeset/young-poets-change.md @@ -0,0 +1,6 @@ +--- +"@gradio/client": patch +"gradio": patch +--- + +fix:Handle spaces using `state` in the JS Client From 80d7b150d71cd20a03ddb08d96f82951ba122e52 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 01:22:33 +0200 Subject: [PATCH 03/13] test --- client/js/src/utils/stream.ts | 4 +--- client/js/src/utils/submit.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/js/src/utils/stream.ts b/client/js/src/utils/stream.ts index 9134d2eb880cb..02df1a968919d 100644 --- a/client/js/src/utils/stream.ts +++ b/client/js/src/utils/stream.ts @@ -51,9 +51,7 @@ export async function open_stream(this: Client): Promise { } else if (event_callbacks[event_id] && config) { if ( _data.msg === "process_completed" && - ["sse", "sse_v1", "sse_v2", "sse_v2.1", "sse_v3"].includes( - config.protocol - ) + ["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol) ) { unclosed_events.delete(event_id); if (unclosed_events.size === 0) { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index f092d5dc21865..f4d795d6a128e 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -693,7 +693,7 @@ export function submit( fn_index, time: new Date() }); - if (["sse_v2", "sse_v2.1", "sse_v3"].includes(protocol)) { + if (["sse_v2", "sse_v2.1"].includes(protocol)) { close_stream(stream_status, stream); stream_status.open = false; } From 26939e7a5f3f00fa72853ad03b530e3afc964992 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 13:27:46 +0200 Subject: [PATCH 04/13] remove state value from payload from server --- client/js/src/helpers/data.ts | 40 +++++++++++- client/js/src/test/data.test.ts | 108 +++++++++++++++++++++++++++++++- client/js/src/utils/submit.ts | 41 +++++------- 3 files changed, 163 insertions(+), 26 deletions(-) diff --git a/client/js/src/helpers/data.ts b/client/js/src/helpers/data.ts index f1840ef766c20..22431dc821774 100644 --- a/client/js/src/helpers/data.ts +++ b/client/js/src/helpers/data.ts @@ -5,7 +5,9 @@ import type { Config, EndpointInfo, JsApiData, - DataType + DataType, + Dependency, + ComponentMeta } from "../types"; export function update_object( @@ -118,3 +120,39 @@ export function post_message( window.parent.postMessage(message, origin, [channel.port2]); }); } + +/** + * Handles the payload by filtering out state inputs and returning an array of resolved payload values. + * We send null values for state inputs to the server, but we don't want to include them in the resolved payload. + * + * @param resolved_payload - The resolved payload values received from the client or the server + * @param dependency - The dependency object. + * @param components - The array of component metadata. + * @param with_null_state - Optional. Specifies whether to include null values for state inputs. Default is false. + * @returns An array of resolved payload values, filtered based on the dependency and component metadata. + */ +export function handle_payload( + resolved_payload: unknown[], + dependency: Dependency, + components: ComponentMeta[], + with_null_state = false +): unknown[] { + let payload_index = 0; + let updated_payload: unknown[] = []; + + dependency.inputs.forEach((input_id) => { + const component = components.find((c) => c.id === input_id); + if (component?.type === "state") { + if (with_null_state) { + updated_payload.push(null); + } + if (!with_null_state) payload_index++; + } else { + const value = resolved_payload[payload_index]; + updated_payload.push(value); + payload_index++; + } + }); + + return updated_payload; +} diff --git a/client/js/src/test/data.test.ts b/client/js/src/test/data.test.ts index 82f8fb36e6db2..91fc09fad54b8 100644 --- a/client/js/src/test/data.test.ts +++ b/client/js/src/test/data.test.ts @@ -3,7 +3,8 @@ import { update_object, walk_and_store_blobs, skip_queue, - post_message + post_message, + handle_payload } from "../helpers/data"; import { NodeBlob } from "../client"; import { config_response, endpoint_info } from "./test_data"; @@ -276,3 +277,108 @@ describe("post_message", () => { ]); }); }); + +describe("handle_payload", () => { + it("should return a payload with null in place of `state` when with_null_state is true", () => { + const resolved_payload = [2]; + const dependency = { + inputs: [1, 2] + }; + const components = [ + { id: 1, type: "number" }, + { id: 2, type: "state" } + ]; + const with_null_state = true; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + with_null_state + ); + expect(result).toEqual([2, null]); + }); + it("should return a payload with null in place of two `state` components when with_null_state is true", () => { + const resolved_payload = ["hello", "goodbye"]; + const dependency = { + inputs: [1, 2, 3, 4] + }; + const components = [ + { id: 1, type: "textbox" }, + { id: 2, type: "state" }, + { id: 3, type: "textbox" }, + { id: 4, type: "state" } + ]; + const with_null_state = true; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + with_null_state + ); + expect(result).toEqual(["hello", null, "goodbye", null]); + }); + + it("should return a payload without the state component value when with_null_state is false", () => { + const resolved_payload = ["hello", null]; + const dependency = { + inputs: [2, 3] + }; + const components = [ + { id: 2, type: "textbox" }, + { id: 3, type: "state" } + ]; + const with_null_state = false; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + with_null_state + ); + expect(result).toEqual(["hello"]); + }); + + it("should return a payload without the two state component values when with_null_state is false", () => { + const resolved_payload = ["hello", null, "world", null]; + const dependency = { + inputs: [2, 3, 4, 5] + }; + const components = [ + { id: 2, type: "textbox" }, + { id: 3, type: "state" }, + { id: 4, type: "textbox" }, + { id: 5, type: "state" } + ]; + const with_null_state = false; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + with_null_state + ); + expect(result).toEqual(["hello", "world"]); + }); + + it("should return the same payload where no state components are defined", () => { + const resolved_payload = ["hello", "world"]; + const dependency = { + inputs: [2, 3] + }; + const components = [ + { id: 2, type: "textbox" }, + { id: 3, type: "textbox" } + ]; + const with_null_state = true; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + with_null_state + ); + expect(result).toEqual(["hello", "world"]); + }); +}); diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index f4d795d6a128e..4ee5bbf12a77f 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -11,11 +11,10 @@ import type { EndpointInfo, ApiInfo, Config, - Dependency, - ComponentMeta + Dependency } from "../types"; -import { skip_queue, post_message } from "../helpers/data"; +import { skip_queue, post_message, handle_payload } from "../helpers/data"; import { resolve_root } from "../helpers/init_helpers"; import { handle_message, @@ -192,24 +191,14 @@ export function submit( }); } - // Builds the payload for submitting data to the server, replacing state components with null - function build_payload( - resolved_payload: unknown[], - dependency: Dependency, - components: ComponentMeta[] - ): any[] { - return dependency.inputs.map((input_id: number, index: number) => { - const component = components.find((c: any) => c.id === input_id); - if (component?.type === "state") { - return null; - } - return resolved_payload[index]; - }); - } - this.handle_blob(config.root, resolved_data, endpoint_info).then( async (_payload) => { - let input_data = build_payload(_payload, dependency, config.components); + let input_data = handle_payload( + _payload, + dependency, + config.components, + true + ); payload = { data: input_data || [], event_data, @@ -242,7 +231,7 @@ export function submit( type: "data", endpoint: _endpoint, fn_index, - data: data, + data: handle_payload(data, dependency, config.components), time: new Date(), event_data, trigger_id @@ -376,7 +365,7 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: data.data, + data: handle_payload(data.data, dependency, config.components), endpoint: _endpoint, fn_index, event_data, @@ -499,7 +488,7 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: data.data, + data: handle_payload(data.data, dependency, config.components), endpoint: _endpoint, fn_index, event_data, @@ -650,7 +639,11 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: data.data, + data: handle_payload( + data.data, + dependency, + config.components + ), endpoint: _endpoint, fn_index }); @@ -693,7 +686,7 @@ export function submit( fn_index, time: new Date() }); - if (["sse_v2", "sse_v2.1"].includes(protocol)) { + if (["sse_v2", "sse_v2.1", ""].includes(protocol)) { close_stream(stream_status, stream); stream_status.open = false; } From cfdfb4e9a04d53660239efcd2356a34508228f72 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 15:49:48 +0200 Subject: [PATCH 05/13] tweak --- client/js/src/utils/submit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index 4ee5bbf12a77f..a611d27f435e9 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -686,7 +686,7 @@ export function submit( fn_index, time: new Date() }); - if (["sse_v2", "sse_v2.1", ""].includes(protocol)) { + if (["sse_v2", "sse_v2.1"].includes(protocol)) { close_stream(stream_status, stream); stream_status.open = false; } From 182045ec7ca9448cbff22621dcc087b8487db8d1 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 16:29:38 +0200 Subject: [PATCH 06/13] test --- client/js/src/utils/submit.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index a611d27f435e9..97230192cb5ed 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -639,11 +639,7 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: handle_payload( - data.data, - dependency, - config.components - ), + data: data.data, endpoint: _endpoint, fn_index }); From 70e074dfdddc2801d78176654675a076b7d89c1e Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 19:02:07 +0200 Subject: [PATCH 07/13] test --- client/js/src/helpers/data.ts | 48 +++++++++++++++++++++++++---------- client/js/src/utils/stream.ts | 4 ++- client/js/src/utils/submit.ts | 19 +++++++------- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/client/js/src/helpers/data.ts b/client/js/src/helpers/data.ts index 22431dc821774..d6edf43835e84 100644 --- a/client/js/src/helpers/data.ts +++ b/client/js/src/helpers/data.ts @@ -136,23 +136,45 @@ export function handle_payload( dependency: Dependency, components: ComponentMeta[], with_null_state = false -): unknown[] { - let payload_index = 0; +): any[] { + try { + let payload_index = 0; + let updated_payload: unknown[] = []; + + dependency.inputs.forEach((input_id) => { + const component = components.find((c) => c.id === input_id); + if (component?.type === "state") { + if (with_null_state) { + updated_payload.push(null); + } + if (!with_null_state) payload_index++; + } else { + const value = resolved_payload[payload_index]; + updated_payload.push(value); + payload_index++; + } + }); + + return updated_payload; + } catch (e) { + console.error(e); + return resolved_payload; + } +} + +export function build_payload( + resolved_payload: unknown[], + dependency: Dependency, + components: ComponentMeta[] +): any[] { let updated_payload: unknown[] = []; - dependency.inputs.forEach((input_id) => { - const component = components.find((c) => c.id === input_id); + dependency.inputs.map((input_id: number, index: number): any => { + const component = components.find((c: any) => c.id === input_id); if (component?.type === "state") { - if (with_null_state) { - updated_payload.push(null); - } - if (!with_null_state) payload_index++; - } else { - const value = resolved_payload[payload_index]; - updated_payload.push(value); - payload_index++; + return null; } + updated_payload.push(resolved_payload[index]); }); - return updated_payload; } diff --git a/client/js/src/utils/stream.ts b/client/js/src/utils/stream.ts index 02df1a968919d..9134d2eb880cb 100644 --- a/client/js/src/utils/stream.ts +++ b/client/js/src/utils/stream.ts @@ -51,7 +51,9 @@ export async function open_stream(this: Client): Promise { } else if (event_callbacks[event_id] && config) { if ( _data.msg === "process_completed" && - ["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol) + ["sse", "sse_v1", "sse_v2", "sse_v2.1", "sse_v3"].includes( + config.protocol + ) ) { unclosed_events.delete(event_id); if (unclosed_events.size === 0) { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index 97230192cb5ed..53467933dd7ff 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -14,7 +14,12 @@ import type { Dependency } from "../types"; -import { skip_queue, post_message, handle_payload } from "../helpers/data"; +import { + skip_queue, + post_message, + handle_payload, + build_payload +} from "../helpers/data"; import { resolve_root } from "../helpers/init_helpers"; import { handle_message, @@ -193,14 +198,9 @@ export function submit( this.handle_blob(config.root, resolved_data, endpoint_info).then( async (_payload) => { - let input_data = handle_payload( - _payload, - dependency, - config.components, - true - ); + let input_data = build_payload(_payload, dependency, config.components); payload = { - data: input_data || [], + data: input_data || _payload || [], event_data, fn_index, trigger_id @@ -231,7 +231,8 @@ export function submit( type: "data", endpoint: _endpoint, fn_index, - data: handle_payload(data, dependency, config.components), + // data: handle_payload(data, dependency, config.components), + data: data, time: new Date(), event_data, trigger_id From 896cf95b012f0554081977d858ec677389d8ad8f Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 21:33:42 +0200 Subject: [PATCH 08/13] Revert "test" This reverts commit 182045ec7ca9448cbff22621dcc087b8487db8d1. --- client/js/src/utils/submit.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index 53467933dd7ff..81e345255148e 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -640,7 +640,11 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: data.data, + data: handle_payload( + data.data, + dependency, + config.components + ), endpoint: _endpoint, fn_index }); From de05487694d23fed993c5b8d557baf04dec66426 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 4 Jun 2024 21:34:17 +0200 Subject: [PATCH 09/13] Revert "test" This reverts commit 70e074dfdddc2801d78176654675a076b7d89c1e. --- client/js/src/helpers/data.ts | 48 ++++++++++------------------------- client/js/src/utils/stream.ts | 4 +-- client/js/src/utils/submit.ts | 19 +++++++------- 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/client/js/src/helpers/data.ts b/client/js/src/helpers/data.ts index d6edf43835e84..22431dc821774 100644 --- a/client/js/src/helpers/data.ts +++ b/client/js/src/helpers/data.ts @@ -136,45 +136,23 @@ export function handle_payload( dependency: Dependency, components: ComponentMeta[], with_null_state = false -): any[] { - try { - let payload_index = 0; - let updated_payload: unknown[] = []; - - dependency.inputs.forEach((input_id) => { - const component = components.find((c) => c.id === input_id); - if (component?.type === "state") { - if (with_null_state) { - updated_payload.push(null); - } - if (!with_null_state) payload_index++; - } else { - const value = resolved_payload[payload_index]; - updated_payload.push(value); - payload_index++; - } - }); - - return updated_payload; - } catch (e) { - console.error(e); - return resolved_payload; - } -} - -export function build_payload( - resolved_payload: unknown[], - dependency: Dependency, - components: ComponentMeta[] -): any[] { +): unknown[] { + let payload_index = 0; let updated_payload: unknown[] = []; - dependency.inputs.map((input_id: number, index: number): any => { - const component = components.find((c: any) => c.id === input_id); + dependency.inputs.forEach((input_id) => { + const component = components.find((c) => c.id === input_id); if (component?.type === "state") { - return null; + if (with_null_state) { + updated_payload.push(null); + } + if (!with_null_state) payload_index++; + } else { + const value = resolved_payload[payload_index]; + updated_payload.push(value); + payload_index++; } - updated_payload.push(resolved_payload[index]); }); + return updated_payload; } diff --git a/client/js/src/utils/stream.ts b/client/js/src/utils/stream.ts index 9134d2eb880cb..02df1a968919d 100644 --- a/client/js/src/utils/stream.ts +++ b/client/js/src/utils/stream.ts @@ -51,9 +51,7 @@ export async function open_stream(this: Client): Promise { } else if (event_callbacks[event_id] && config) { if ( _data.msg === "process_completed" && - ["sse", "sse_v1", "sse_v2", "sse_v2.1", "sse_v3"].includes( - config.protocol - ) + ["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol) ) { unclosed_events.delete(event_id); if (unclosed_events.size === 0) { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index 81e345255148e..a611d27f435e9 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -14,12 +14,7 @@ import type { Dependency } from "../types"; -import { - skip_queue, - post_message, - handle_payload, - build_payload -} from "../helpers/data"; +import { skip_queue, post_message, handle_payload } from "../helpers/data"; import { resolve_root } from "../helpers/init_helpers"; import { handle_message, @@ -198,9 +193,14 @@ export function submit( this.handle_blob(config.root, resolved_data, endpoint_info).then( async (_payload) => { - let input_data = build_payload(_payload, dependency, config.components); + let input_data = handle_payload( + _payload, + dependency, + config.components, + true + ); payload = { - data: input_data || _payload || [], + data: input_data || [], event_data, fn_index, trigger_id @@ -231,8 +231,7 @@ export function submit( type: "data", endpoint: _endpoint, fn_index, - // data: handle_payload(data, dependency, config.components), - data: data, + data: handle_payload(data, dependency, config.components), time: new Date(), event_data, trigger_id From 7877a86f680f696a1bd50f14cc65e8d68ef658bf Mon Sep 17 00:00:00 2001 From: pngwn Date: Tue, 4 Jun 2024 23:29:48 +0100 Subject: [PATCH 10/13] fixes --- client/js/src/helpers/data.ts | 35 ++++++++++++++--- client/js/src/test/data.test.ts | 35 +++++++++++++++-- client/js/src/types.ts | 1 + client/js/src/utils/submit.ts | 32 +++++++++++++--- js/app/src/Blocks.svelte | 68 ++++++++++++++++----------------- js/app/src/Index.svelte | 3 +- js/preview/src/dev.ts | 2 +- 7 files changed, 125 insertions(+), 51 deletions(-) diff --git a/client/js/src/helpers/data.ts b/client/js/src/helpers/data.ts index 22431dc821774..88467e4043154 100644 --- a/client/js/src/helpers/data.ts +++ b/client/js/src/helpers/data.ts @@ -135,24 +135,47 @@ export function handle_payload( resolved_payload: unknown[], dependency: Dependency, components: ComponentMeta[], + type: "input" | "output", with_null_state = false ): unknown[] { - let payload_index = 0; - let updated_payload: unknown[] = []; + if (type === "input" && !with_null_state) { + throw new Error("Invalid code path. Cannot skip state inputs for input."); + } + // data comes from the server with null state values so we skip + if (type === "output" && with_null_state) { + return resolved_payload; + } - dependency.inputs.forEach((input_id) => { + let updated_payload: unknown[] = []; + let payload_index = 0; + for (let i = 0; i < dependency.inputs.length; i++) { + const input_id = dependency.inputs[i]; const component = components.find((c) => c.id === input_id); + if (component?.type === "state") { + // input + with_null_state needs us to fill state with null values if (with_null_state) { - updated_payload.push(null); + if (resolved_payload.length === dependency.inputs.length) { + const value = resolved_payload[payload_index]; + updated_payload.push(value); + payload_index++; + } else { + updated_payload.push(null); + } + } else { + // this is output & !with_null_state, we skip state inputs + // the server payload always comes with null state values so we move along the payload index + payload_index++; + continue; } - if (!with_null_state) payload_index++; + // input & !with_null_state isn't a case we care about, server needs null + continue; } else { const value = resolved_payload[payload_index]; updated_payload.push(value); payload_index++; } - }); + } return updated_payload; } diff --git a/client/js/src/test/data.test.ts b/client/js/src/test/data.test.ts index 91fc09fad54b8..b2587fb38ecac 100644 --- a/client/js/src/test/data.test.ts +++ b/client/js/src/test/data.test.ts @@ -279,7 +279,7 @@ describe("post_message", () => { }); describe("handle_payload", () => { - it("should return a payload with null in place of `state` when with_null_state is true", () => { + it("should return an input payload with null in place of `state` when with_null_state is true", () => { const resolved_payload = [2]; const dependency = { inputs: [1, 2] @@ -294,11 +294,12 @@ describe("handle_payload", () => { // @ts-ignore dependency, components, + "input", with_null_state ); expect(result).toEqual([2, null]); }); - it("should return a payload with null in place of two `state` components when with_null_state is true", () => { + it("should return an input payload with null in place of two `state` components when with_null_state is true", () => { const resolved_payload = ["hello", "goodbye"]; const dependency = { inputs: [1, 2, 3, 4] @@ -315,12 +316,13 @@ describe("handle_payload", () => { // @ts-ignore dependency, components, + "input", with_null_state ); expect(result).toEqual(["hello", null, "goodbye", null]); }); - it("should return a payload without the state component value when with_null_state is false", () => { + it("should return an output payload without the state component value when with_null_state is false", () => { const resolved_payload = ["hello", null]; const dependency = { inputs: [2, 3] @@ -335,12 +337,13 @@ describe("handle_payload", () => { // @ts-ignore dependency, components, + "output", with_null_state ); expect(result).toEqual(["hello"]); }); - it("should return a payload without the two state component values when with_null_state is false", () => { + it("should return an ouput payload without the two state component values when with_null_state is false", () => { const resolved_payload = ["hello", null, "world", null]; const dependency = { inputs: [2, 3, 4, 5] @@ -357,11 +360,35 @@ describe("handle_payload", () => { // @ts-ignore dependency, components, + "output", with_null_state ); expect(result).toEqual(["hello", "world"]); }); + it("should return an ouput payload with the two state component values when with_null_state is true", () => { + const resolved_payload = ["hello", null, "world", null]; + const dependency = { + inputs: [2, 3, 4, 5] + }; + const components = [ + { id: 2, type: "textbox" }, + { id: 3, type: "state" }, + { id: 4, type: "textbox" }, + { id: 5, type: "state" } + ]; + const with_null_state = true; + const result = handle_payload( + resolved_payload, + // @ts-ignore + dependency, + components, + "output", + with_null_state + ); + expect(result).toEqual(["hello", null, "world", null]); + }); + it("should return the same payload where no state components are defined", () => { const resolved_payload = ["hello", "world"]; const dependency = { diff --git a/client/js/src/types.ts b/client/js/src/types.ts index b04ac264831bd..9b8605423afb0 100644 --- a/client/js/src/types.ts +++ b/client/js/src/types.ts @@ -267,6 +267,7 @@ export interface ClientOptions { hf_token?: `hf_${string}`; status_callback?: SpaceStatusCallback | null; auth?: [string, string] | null; + with_null_state?: boolean; } export interface FileData { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index a611d27f435e9..212ea55f66b18 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -47,7 +47,8 @@ export function submit( pending_diff_streams, event_callbacks, unclosed_events, - post_data + post_data, + options } = this; if (!api_info) throw new Error("No API found"); @@ -197,6 +198,7 @@ export function submit( _payload, dependency, config.components, + "input", true ); payload = { @@ -231,7 +233,13 @@ export function submit( type: "data", endpoint: _endpoint, fn_index, - data: handle_payload(data, dependency, config.components), + data: handle_payload( + data, + dependency, + config.components, + "output", + options.with_null_state + ), time: new Date(), event_data, trigger_id @@ -365,7 +373,13 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: handle_payload(data.data, dependency, config.components), + data: handle_payload( + data.data, + dependency, + config.components, + "output", + options.with_null_state + ), endpoint: _endpoint, fn_index, event_data, @@ -488,7 +502,13 @@ export function submit( fire_event({ type: "data", time: new Date(), - data: handle_payload(data.data, dependency, config.components), + data: handle_payload( + data.data, + dependency, + config.components, + "output", + options.with_null_state + ), endpoint: _endpoint, fn_index, event_data, @@ -642,7 +662,9 @@ export function submit( data: handle_payload( data.data, dependency, - config.components + config.components, + "output", + options.with_null_state ), endpoint: _endpoint, fn_index diff --git a/js/app/src/Blocks.svelte b/js/app/src/Blocks.svelte index 65e77e796cacd..2e63550d48731 100644 --- a/js/app/src/Blocks.svelte +++ b/js/app/src/Blocks.svelte @@ -48,7 +48,7 @@ loading_status, scheduled_updates, create_layout, - rerender_layout + rerender_layout, } = create_components(); $: create_layout({ @@ -58,8 +58,8 @@ root, app, options: { - fill_height - } + fill_height, + }, }); $: { @@ -90,7 +90,7 @@ return { id: outputs[i], prop: "value_is_output", - value: true + value: true, }; }); @@ -113,7 +113,7 @@ updates.push({ id: outputs[i], prop: update_key, - value: update_value + value: update_value, }); } } @@ -121,7 +121,7 @@ updates.push({ id: outputs[i], prop: "value", - value + value, }); } }); @@ -136,19 +136,19 @@ function new_message( message: string, fn_index: number, - type: ToastMessage["type"] + type: ToastMessage["type"], ): ToastMessage & { fn_index: number } { return { message, fn_index, type, - id: ++_error_id + id: ++_error_id, }; } export function add_new_message( message: string, - type: ToastMessage["type"] + type: ToastMessage["type"], ): void { messages = [new_message(message, -1, type), ...messages]; } @@ -171,7 +171,7 @@ const SHOW_MOBILE_QUEUE_WARNING_ON_ETA = 10; const is_mobile_device = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent + navigator.userAgent, ); let showed_duplicate_message = false; let showed_mobile_warning = false; @@ -180,7 +180,7 @@ function wait_then_trigger_api_call( dep_index: number, trigger_id: number | null = null, - event_data: unknown = null + event_data: unknown = null, ): void { let _unsub = (): void => {}; function unsub(): void { @@ -201,7 +201,7 @@ async function trigger_api_call( dep_index: number, trigger_id: number | null = null, - event_data: unknown = null + event_data: unknown = null, ): Promise { let dep = dependencies.find((dep) => dep.id === dep_index)!; @@ -213,7 +213,7 @@ const submission = submit_map.get(fn_index); submission?.cancel(); return submission; - }) + }), ); } if (current_status === "pending" || current_status === "generating") { @@ -224,15 +224,15 @@ fn_index: dep_index, data: await Promise.all(dep.inputs.map((id) => get_data(id))), event_data: dep.collects_event_data ? event_data : null, - trigger_id: trigger_id + trigger_id: trigger_id, }; if (dep.frontend_fn) { dep .frontend_fn( payload.data.concat( - await Promise.all(dep.outputs.map((id) => get_data(id))) - ) + await Promise.all(dep.outputs.map((id) => get_data(id))), + ), ) .then((v: unknown[]) => { if (dep.backend_fn) { @@ -269,7 +269,7 @@ payload.fn_index, payload.data as unknown[], payload.event_data, - payload.trigger_id + payload.trigger_id, ); } catch (e) { const fn_index = 0; // Mock value for fn_index @@ -279,7 +279,7 @@ fn_index, eta: 0, queue: false, - queue_position: null + queue_position: null, }); set_status($loading_status); return; @@ -319,7 +319,7 @@ layout: render_layout, root: root, dependencies: dependencies, - render_id: render_id + render_id: render_id, }); }) .on("status", ({ fn_index, ...status }) => { @@ -328,7 +328,7 @@ ...status, status: status.stage, progress: status.progress_data, - fn_index + fn_index, }); set_status($loading_status); if ( @@ -342,7 +342,7 @@ showed_duplicate_message = true; messages = [ new_message(DUPLICATE_MESSAGE, fn_index, "warning"), - ...messages + ...messages, ]; } if ( @@ -354,7 +354,7 @@ showed_mobile_warning = true; messages = [ new_message(MOBILE_QUEUE_WARNING, fn_index, "warning"), - ...messages + ...messages, ]; } @@ -378,7 +378,7 @@ window.setTimeout(() => { messages = [ new_message(MOBILE_RECONNECT_MESSAGE, fn_index, "error"), - ...messages + ...messages, ]; }, 0); wait_then_trigger_api_call(dep.id, payload.trigger_id, event_data); @@ -387,11 +387,11 @@ if (status.message) { const _message = status.message.replace( MESSAGE_QUOTE_RE, - (_, b) => b + (_, b) => b, ); messages = [ new_message(_message, fn_index, "error"), - ...messages + ...messages, ]; } dependencies.map(async (dep) => { @@ -419,7 +419,7 @@ return; } const discussion_url = new URL( - `https://huggingface.co/spaces/${space_id}/discussions/new` + `https://huggingface.co/spaces/${space_id}/discussions/new`, ); if (title !== undefined && title.length > 0) { discussion_url.searchParams.set("title", title); @@ -440,7 +440,7 @@ if (js) { let blocks_frontend_fn = new AsyncFunction( `let result = await (${js})(); - return (!Array.isArray(result)) ? [result] : result;` + return (!Array.isArray(result)) ? [result] : result;`, ); await blocks_frontend_fn(); } @@ -503,15 +503,15 @@ function update_status( id: number, status: "error" | "complete" | "pending", - data: LoadingStatus + data: LoadingStatus, ): void { data.status = status; update_value([ { id, prop: "loading_status", - value: data - } + value: data, + }, ]); } @@ -523,7 +523,7 @@ }[] = []; Object.entries(statuses).forEach(([id, loading_status]) => { let dependency = dependencies.find( - (dep) => dep.id == loading_status.fn_index + (dep) => dep.id == loading_status.fn_index, ); if (dependency === undefined) { return; @@ -533,7 +533,7 @@ updates.push({ id: parseInt(id), prop: "loading_status", - value: loading_status + value: loading_status, }); }); @@ -543,9 +543,9 @@ return { id, prop: "pending", - value: pending_status === "pending" + value: pending_status === "pending", }; - } + }, ); update_value([...updates, ...additional_updates]); diff --git a/js/app/src/Index.svelte b/js/app/src/Index.svelte index 87324dc777af8..eb806a18010f4 100644 --- a/js/app/src/Index.svelte +++ b/js/app/src/Index.svelte @@ -274,7 +274,8 @@ : host || space || src || location.origin; app = await Client.connect(api_url, { - status_callback: handle_status + status_callback: handle_status, + with_null_state: true }); if (!app.config) { diff --git a/js/preview/src/dev.ts b/js/preview/src/dev.ts index 543d07a0a48e8..2428fbf69c4bd 100644 --- a/js/preview/src/dev.ts +++ b/js/preview/src/dev.ts @@ -101,7 +101,7 @@ function find_frontend_folders(start_path: string): string[] { function to_posix(_path: string): string { const isExtendedLengthPath = /^\\\\\?\\/.test(_path); - const hasNonAscii = /[^\u0000-\u0080]+/.test(_path); // eslint-disable-line no-control-regex + const hasNonAscii = /[^\u0000-\u0080]+/.test(_path); if (isExtendedLengthPath || hasNonAscii) { return _path; From e6108e87859fbba0125b411e818ab36dee0caf29 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 4 Jun 2024 22:30:36 +0000 Subject: [PATCH 11/13] add changeset --- .changeset/young-poets-change.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/young-poets-change.md b/.changeset/young-poets-change.md index b7667a07d1642..e1c87d0db382e 100644 --- a/.changeset/young-poets-change.md +++ b/.changeset/young-poets-change.md @@ -1,5 +1,7 @@ --- +"@gradio/app": patch "@gradio/client": patch +"@gradio/preview": patch "gradio": patch --- From 90e81eb4d3c85ed8e816ce87028bfb1ce6006dc8 Mon Sep 17 00:00:00 2001 From: pngwn Date: Tue, 4 Jun 2024 23:30:34 +0100 Subject: [PATCH 12/13] fixes --- js/app/src/Blocks.svelte | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/js/app/src/Blocks.svelte b/js/app/src/Blocks.svelte index 2e63550d48731..65e77e796cacd 100644 --- a/js/app/src/Blocks.svelte +++ b/js/app/src/Blocks.svelte @@ -48,7 +48,7 @@ loading_status, scheduled_updates, create_layout, - rerender_layout, + rerender_layout } = create_components(); $: create_layout({ @@ -58,8 +58,8 @@ root, app, options: { - fill_height, - }, + fill_height + } }); $: { @@ -90,7 +90,7 @@ return { id: outputs[i], prop: "value_is_output", - value: true, + value: true }; }); @@ -113,7 +113,7 @@ updates.push({ id: outputs[i], prop: update_key, - value: update_value, + value: update_value }); } } @@ -121,7 +121,7 @@ updates.push({ id: outputs[i], prop: "value", - value, + value }); } }); @@ -136,19 +136,19 @@ function new_message( message: string, fn_index: number, - type: ToastMessage["type"], + type: ToastMessage["type"] ): ToastMessage & { fn_index: number } { return { message, fn_index, type, - id: ++_error_id, + id: ++_error_id }; } export function add_new_message( message: string, - type: ToastMessage["type"], + type: ToastMessage["type"] ): void { messages = [new_message(message, -1, type), ...messages]; } @@ -171,7 +171,7 @@ const SHOW_MOBILE_QUEUE_WARNING_ON_ETA = 10; const is_mobile_device = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent, + navigator.userAgent ); let showed_duplicate_message = false; let showed_mobile_warning = false; @@ -180,7 +180,7 @@ function wait_then_trigger_api_call( dep_index: number, trigger_id: number | null = null, - event_data: unknown = null, + event_data: unknown = null ): void { let _unsub = (): void => {}; function unsub(): void { @@ -201,7 +201,7 @@ async function trigger_api_call( dep_index: number, trigger_id: number | null = null, - event_data: unknown = null, + event_data: unknown = null ): Promise { let dep = dependencies.find((dep) => dep.id === dep_index)!; @@ -213,7 +213,7 @@ const submission = submit_map.get(fn_index); submission?.cancel(); return submission; - }), + }) ); } if (current_status === "pending" || current_status === "generating") { @@ -224,15 +224,15 @@ fn_index: dep_index, data: await Promise.all(dep.inputs.map((id) => get_data(id))), event_data: dep.collects_event_data ? event_data : null, - trigger_id: trigger_id, + trigger_id: trigger_id }; if (dep.frontend_fn) { dep .frontend_fn( payload.data.concat( - await Promise.all(dep.outputs.map((id) => get_data(id))), - ), + await Promise.all(dep.outputs.map((id) => get_data(id))) + ) ) .then((v: unknown[]) => { if (dep.backend_fn) { @@ -269,7 +269,7 @@ payload.fn_index, payload.data as unknown[], payload.event_data, - payload.trigger_id, + payload.trigger_id ); } catch (e) { const fn_index = 0; // Mock value for fn_index @@ -279,7 +279,7 @@ fn_index, eta: 0, queue: false, - queue_position: null, + queue_position: null }); set_status($loading_status); return; @@ -319,7 +319,7 @@ layout: render_layout, root: root, dependencies: dependencies, - render_id: render_id, + render_id: render_id }); }) .on("status", ({ fn_index, ...status }) => { @@ -328,7 +328,7 @@ ...status, status: status.stage, progress: status.progress_data, - fn_index, + fn_index }); set_status($loading_status); if ( @@ -342,7 +342,7 @@ showed_duplicate_message = true; messages = [ new_message(DUPLICATE_MESSAGE, fn_index, "warning"), - ...messages, + ...messages ]; } if ( @@ -354,7 +354,7 @@ showed_mobile_warning = true; messages = [ new_message(MOBILE_QUEUE_WARNING, fn_index, "warning"), - ...messages, + ...messages ]; } @@ -378,7 +378,7 @@ window.setTimeout(() => { messages = [ new_message(MOBILE_RECONNECT_MESSAGE, fn_index, "error"), - ...messages, + ...messages ]; }, 0); wait_then_trigger_api_call(dep.id, payload.trigger_id, event_data); @@ -387,11 +387,11 @@ if (status.message) { const _message = status.message.replace( MESSAGE_QUOTE_RE, - (_, b) => b, + (_, b) => b ); messages = [ new_message(_message, fn_index, "error"), - ...messages, + ...messages ]; } dependencies.map(async (dep) => { @@ -419,7 +419,7 @@ return; } const discussion_url = new URL( - `https://huggingface.co/spaces/${space_id}/discussions/new`, + `https://huggingface.co/spaces/${space_id}/discussions/new` ); if (title !== undefined && title.length > 0) { discussion_url.searchParams.set("title", title); @@ -440,7 +440,7 @@ if (js) { let blocks_frontend_fn = new AsyncFunction( `let result = await (${js})(); - return (!Array.isArray(result)) ? [result] : result;`, + return (!Array.isArray(result)) ? [result] : result;` ); await blocks_frontend_fn(); } @@ -503,15 +503,15 @@ function update_status( id: number, status: "error" | "complete" | "pending", - data: LoadingStatus, + data: LoadingStatus ): void { data.status = status; update_value([ { id, prop: "loading_status", - value: data, - }, + value: data + } ]); } @@ -523,7 +523,7 @@ }[] = []; Object.entries(statuses).forEach(([id, loading_status]) => { let dependency = dependencies.find( - (dep) => dep.id == loading_status.fn_index, + (dep) => dep.id == loading_status.fn_index ); if (dependency === undefined) { return; @@ -533,7 +533,7 @@ updates.push({ id: parseInt(id), prop: "loading_status", - value: loading_status, + value: loading_status }); }); @@ -543,9 +543,9 @@ return { id, prop: "pending", - value: pending_status === "pending", + value: pending_status === "pending" }; - }, + } ); update_value([...updates, ...additional_updates]); From e575ab2b553567abf55d922993d4e01d9b0deeca Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 4 Jun 2024 22:55:25 +0000 Subject: [PATCH 13/13] add changeset --- .changeset/young-poets-change.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/young-poets-change.md b/.changeset/young-poets-change.md index e1c87d0db382e..41ff2c264b836 100644 --- a/.changeset/young-poets-change.md +++ b/.changeset/young-poets-change.md @@ -5,4 +5,4 @@ "gradio": patch --- -fix:Handle spaces using `state` in the JS Client +fix:Handle gradio apps using `state` in the JS Client