@gradio/client 2.5.1 → 2.7.0

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.
@@ -7,7 +7,6 @@ import {
7
7
  expect,
8
8
  vi
9
9
  } from "vitest";
10
-
11
10
  import { Client, client, duplicate } from "..";
12
11
  import {
13
12
  transformed_api_info,
@@ -16,6 +15,7 @@ import {
16
15
  } from "./test_data";
17
16
  import { initialise_server } from "./server";
18
17
  import { SPACE_NOT_FOUND_MSG } from "../constants";
18
+ import { HttpResponse, http } from "msw";
19
19
 
20
20
  const app_reference = "hmb/hello_world";
21
21
  const broken_app_reference = "hmb/bye_world";
@@ -65,6 +65,29 @@ describe("Client class", () => {
65
65
  expect(app.config).toEqual(config_response);
66
66
  });
67
67
 
68
+ test("forwards a page query when resolving config and API info", async () => {
69
+ const requested_urls: string[] = [];
70
+ server.use(
71
+ http.get(`${direct_app_reference}/config`, ({ request }) => {
72
+ requested_urls.push(request.url);
73
+ return HttpResponse.json(config_response);
74
+ }),
75
+ http.get(`${direct_app_reference}/info`, ({ request }) => {
76
+ requested_urls.push(request.url);
77
+ return HttpResponse.json(response_api_info);
78
+ })
79
+ );
80
+
81
+ await Client.connect(direct_app_reference, {
82
+ query_params: { page: "details" }
83
+ });
84
+
85
+ expect(requested_urls).toEqual([
86
+ `${direct_app_reference}/config?page=details`,
87
+ `${direct_app_reference}/info?page=details`
88
+ ]);
89
+ });
90
+
68
91
  test("connecting successfully to a private running app with a space reference", async () => {
69
92
  const app = await Client.connect("hmb/secret_world", {
70
93
  token: "hf_123"
@@ -76,6 +99,29 @@ describe("Client class", () => {
76
99
  });
77
100
  });
78
101
 
102
+ test("signs initial file values in a private Space config", async () => {
103
+ const props = config_response.components[0].props;
104
+ props.value = {
105
+ path: "/tmp/cat.png",
106
+ url: `${secret_direct_app_reference}/gradio_api/file=/tmp/cat.png`,
107
+ meta: { _type: "gradio.FileData" }
108
+ };
109
+ try {
110
+ const app = await Client.connect("hmb/secret_world", {
111
+ token: "hf_123"
112
+ });
113
+ const value = app.config?.components[0].props.value as {
114
+ url: string;
115
+ };
116
+
117
+ expect(value.url).toBe(
118
+ `${secret_direct_app_reference}/gradio_api/file=/tmp/cat.png?__sign=jwt_123`
119
+ );
120
+ } finally {
121
+ delete props.value;
122
+ }
123
+ });
124
+
79
125
  test("connecting successfully to a private running app with a direct app URL ", async () => {
80
126
  const app = await Client.connect(secret_direct_app_reference, {
81
127
  token: "hf_123"
@@ -7,7 +7,9 @@ import {
7
7
  delete_run_history,
8
8
  on_run_history_change,
9
9
  read_run_history,
10
+ read_run_history_storage,
10
11
  run_history_url,
12
+ set_run_history_storage,
11
13
  stage_run_history_replay,
12
14
  start_run_history,
13
15
  update_run_history,
@@ -25,6 +27,8 @@ const replacement_apps = Array.from(
25
27
  const in_browser = typeof window !== "undefined";
26
28
 
27
29
  afterEach(() => {
30
+ set_run_history_storage(scope, { type: "browser" });
31
+ set_run_history_storage(other_scope, { type: "browser" });
28
32
  clear_run_history(scope);
29
33
  clear_run_history(other_scope);
30
34
  for (const replacement_app of replacement_apps) {
@@ -34,6 +38,48 @@ afterEach(() => {
34
38
  });
35
39
 
36
40
  describe.skipIf(!in_browser)("run history", () => {
41
+ test("uses this browser as the default history storage", () => {
42
+ expect(read_run_history_storage(scope)).toEqual({ type: "browser" });
43
+ });
44
+
45
+ test("remembers a bucket while switching storage destinations", () => {
46
+ set_run_history_storage(scope, {
47
+ type: "bucket",
48
+ bucket_id: "alice/app-history"
49
+ });
50
+ expect(read_run_history_storage(scope)).toEqual({
51
+ type: "bucket",
52
+ bucket_id: "alice/app-history"
53
+ });
54
+
55
+ set_run_history_storage(scope, {
56
+ type: "browser",
57
+ bucket_id: "alice/app-history"
58
+ });
59
+ expect(read_run_history_storage(scope)).toEqual({
60
+ type: "browser",
61
+ bucket_id: "alice/app-history"
62
+ });
63
+ });
64
+
65
+ test("does not duplicate bucket runs in local storage", () => {
66
+ set_run_history_storage(scope, {
67
+ type: "bucket",
68
+ bucket_id: "alice/app-history"
69
+ });
70
+
71
+ const id = start_run_history({
72
+ app_id,
73
+ endpoint: "/predict",
74
+ api_name: "/predict",
75
+ fn_index: 0,
76
+ inputs: ["hello"]
77
+ });
78
+
79
+ expect(id).toBeNull();
80
+ expect(read_run_history(scope)).toEqual([]);
81
+ });
82
+
37
83
  test("stores runs per app id with the page and inputs", () => {
38
84
  const id = start_run_history({
39
85
  app_id,
@@ -8,6 +8,7 @@ const IS_NODE =
8
8
  interface MockServer {
9
9
  start: (opts: StartOptions) => void | ReturnType<SetupWorker["start"]>;
10
10
  stop: () => void | Promise<void>;
11
+ use: (...handlers: any[]) => void;
11
12
  resetHandlers: (...handlers: any[]) => void;
12
13
  }
13
14
 
@@ -18,6 +19,7 @@ export async function initialise_server(): Promise<MockServer> {
18
19
  return {
19
20
  start: (opts: StartOptions) => server.listen(opts),
20
21
  stop: () => server.close(),
22
+ use: (...h) => server.use(...h),
21
23
  resetHandlers: (...h) => server.resetHandlers(...h)
22
24
  };
23
25
  }
@@ -26,6 +28,7 @@ export async function initialise_server(): Promise<MockServer> {
26
28
  return {
27
29
  start: (opts: StartOptions) => worker.start(opts),
28
30
  stop: () => worker.stop(),
31
+ use: (...h) => worker.use(...h),
29
32
  resetHandlers: (...h) => worker.resetHandlers(...h)
30
33
  };
31
34
  }
@@ -1,6 +1,9 @@
1
1
  import { describe, beforeAll, afterEach, afterAll, test, expect } from "vitest";
2
+ import { HttpResponse, http } from "msw";
2
3
 
3
4
  import { Client } from "../client";
5
+ import { set_run_history_storage } from "../utils/run_history";
6
+ import { direct_space_url } from "./handlers";
4
7
  import { initialise_server } from "./server";
5
8
 
6
9
  let server: Awaited<ReturnType<typeof initialise_server>>;
@@ -9,7 +12,12 @@ beforeAll(async () => {
9
12
  server = await initialise_server();
10
13
  await server.start({ quiet: true });
11
14
  });
12
- afterEach(() => server.resetHandlers());
15
+ afterEach(() => {
16
+ server.resetHandlers();
17
+ if (typeof window !== "undefined") {
18
+ set_run_history_storage({ app_id: 123 }, { type: "browser" });
19
+ }
20
+ });
13
21
  afterAll(() => server.stop());
14
22
 
15
23
  async function race_with_timeout<T>(
@@ -29,6 +37,72 @@ async function race_with_timeout<T>(
29
37
  }
30
38
 
31
39
  describe("submit iterator", () => {
40
+ test("signs private Space file URLs before publishing data events", async () => {
41
+ const app = await Client.connect("hmb/hello_world", {
42
+ token: "hf_123",
43
+ events: ["data", "status"]
44
+ });
45
+ app.stream_status.open = true;
46
+
47
+ const iterator = app.submit("/predict", ["hi"]);
48
+ const event_id = await iterator.wait_for_id();
49
+ const callback = app.event_callbacks[event_id as string];
50
+
51
+ const events: any[] = [];
52
+ const consumer = (async () => {
53
+ for await (const event of iterator) events.push(event);
54
+ })();
55
+
56
+ await callback({
57
+ msg: "process_completed",
58
+ output: {
59
+ data: [
60
+ {
61
+ path: "/tmp/cat.png",
62
+ url: `${direct_space_url}/gradio_api/file=/tmp/cat.png`,
63
+ meta: { _type: "gradio.FileData" }
64
+ }
65
+ ]
66
+ },
67
+ success: true
68
+ });
69
+ await consumer;
70
+
71
+ const data_event = events.find((event) => event.type === "data");
72
+ expect(data_event.data[0].url).toBe(
73
+ `${direct_space_url}/gradio_api/file=/tmp/cat.png?__sign=jwt_123`
74
+ );
75
+ });
76
+
77
+ test.skipIf(typeof window === "undefined")(
78
+ "sends the selected history bucket with queued submissions",
79
+ async () => {
80
+ const app = await Client.connect("hmb/hello_world");
81
+ const scope = {
82
+ app_id: app.config?.app_id,
83
+ username: app.config?.username
84
+ };
85
+ set_run_history_storage(scope, {
86
+ type: "bucket",
87
+ bucket_id: "alice/app-history"
88
+ });
89
+
90
+ let header: string | null = null;
91
+ server.resetHandlers(
92
+ http.post(`${direct_space_url}/queue/join`, ({ request }) => {
93
+ header = request.headers.get("x-gradio-history-bucket");
94
+ return HttpResponse.json({ event_id: "bucket-event" });
95
+ })
96
+ );
97
+
98
+ const iterator = app.submit("/predict", ["hi"]);
99
+ await expect(iterator.wait_for_id()).resolves.toBe("bucket-event");
100
+ expect(header).toBe("alice/app-history");
101
+ await iterator.return();
102
+ set_run_history_storage(scope, { type: "browser" });
103
+ }
104
+ );
105
+
32
106
  test("next() after the iterator is closed resolves to {done: true}", async () => {
33
107
  const app = await Client.connect("hmb/hello_world");
34
108
  // Avoid opening a real SSE stream — the test does not need one.
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import type { Client } from "../client";
4
+ import { FileData, upload } from "../upload";
5
+
6
+ describe("upload", () => {
7
+ it("encodes uploaded file paths in file URLs", async () => {
8
+ const path = "/tmp/computer%20vision#Huggy.png";
9
+ const client = {
10
+ api_prefix: "/gradio_api",
11
+ upload_files: vi.fn().mockResolvedValue({ files: [path] })
12
+ } as unknown as Client;
13
+ const input = new FileData({
14
+ path,
15
+ blob: new File([], "computer%20vision#Huggy.png")
16
+ });
17
+
18
+ const result = await upload.call(client, [input], "https://example.com");
19
+
20
+ expect(result?.[0]).toMatchObject({
21
+ path,
22
+ url: "https://example.com/gradio_api/file=/tmp/computer%2520vision%23Huggy.png"
23
+ });
24
+ });
25
+ });
package/src/types.ts CHANGED
@@ -354,11 +354,11 @@ export interface ClientOptions {
354
354
  */
355
355
  oauth_token?: string;
356
356
  /**
357
- * Whether to save each call's inputs and outputs in this browser's local
358
- * storage, so they can be reviewed on the app's run history page. Defaults
359
- * to true, and only ever applies in a browser: in Node there is no storage
360
- * to write to and nothing is recorded. The app can turn it off for everyone
361
- * with `run_history=False` on `launch()`, which takes precedence over this.
357
+ * Whether to save each call's inputs and outputs to the history destination
358
+ * selected in this browser (local storage by default, or a connected bucket).
359
+ * Defaults to true, and only ever applies in a browser: in Node there is no
360
+ * selected destination and nothing is recorded. The app can turn it off for
361
+ * everyone with `run_history=False` on `launch()`, which takes precedence.
362
362
  */
363
363
  record_history?: boolean;
364
364
  }
package/src/upload.ts CHANGED
@@ -30,10 +30,14 @@ export async function upload(
30
30
  } else {
31
31
  if (response.files) {
32
32
  return response.files.map((f, i) => {
33
+ const encoded_path = f
34
+ .split("/")
35
+ .map((segment) => encodeURIComponent(segment))
36
+ .join("/");
33
37
  const file = new FileData({
34
38
  ...file_data[i],
35
39
  path: f,
36
- url: `${root_url}${this.api_prefix}/file=${f}`
40
+ url: `${root_url}${this.api_prefix}/file=${encoded_path}`
37
41
  });
38
42
  return file;
39
43
  });
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Client for the durable run history in an HF Hub bucket
3
+ * (`/gradio_api/run-history/*`).
4
+ *
5
+ * Read-only by design. Records are written by the server from the run it
6
+ * actually executed — there is no push function here, and nothing in this
7
+ * module can create a record.
8
+ *
9
+ * Every call names the bucket it is talking about. The server holds no
10
+ * per-session binding, so two tabs (or two apps on one origin) cannot end up
11
+ * reading each other's history.
12
+ */
13
+
14
+ const BUCKET_ID_RE = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-][a-zA-Z0-9_./-]*$/;
15
+
16
+ export function is_valid_bucket_id(id: string): boolean {
17
+ if (!BUCKET_ID_RE.test(id)) return false;
18
+ return !id
19
+ .split("/")
20
+ .some((seg) => seg === "" || seg === "." || seg === "..");
21
+ }
22
+
23
+ /** Mirrors `HistoryRecord` in `gradio/history.py`. */
24
+ export interface HistoryRecord {
25
+ record_id: string;
26
+ endpoint: string;
27
+ inputs: unknown;
28
+ outputs: unknown;
29
+ started_at: string;
30
+ schema_version: number;
31
+ }
32
+
33
+ export interface HistoryResult<T> {
34
+ ok: boolean;
35
+ status: number;
36
+ data: T;
37
+ detail?: string;
38
+ }
39
+
40
+ function url(
41
+ root: string,
42
+ path: string,
43
+ params?: Record<string, string>
44
+ ): string {
45
+ const query = params ? `?${new URLSearchParams(params)}` : "";
46
+ const base = root.replace(/\/+$/, "");
47
+ if (base) return `${base}/gradio_api/run-history/${path}${query}`;
48
+ // No configured root. A leading slash would drop any mount subpath
49
+ // (`mount_gradio_app(app, path="/myapp")`), so resolve against the document
50
+ // base, which gradio sets via <base href> on the app shell.
51
+ if (typeof document !== "undefined" && document.baseURI) {
52
+ return new URL(
53
+ `gradio_api/run-history/${path}${query}`,
54
+ document.baseURI
55
+ ).toString();
56
+ }
57
+ return `gradio_api/run-history/${path}${query}`;
58
+ }
59
+
60
+ async function parse_error(res: Response): Promise<string> {
61
+ try {
62
+ const j = await res.json();
63
+ return typeof j?.detail === "string" ? j.detail : `${res.status}`;
64
+ } catch {
65
+ return `${res.status}`;
66
+ }
67
+ }
68
+
69
+ async function request<T>(
70
+ input: string,
71
+ init: RequestInit,
72
+ fallback: T,
73
+ pick: (body: any) => T
74
+ ): Promise<HistoryResult<T>> {
75
+ try {
76
+ const res = await fetch(input, { credentials: "include", ...init });
77
+ if (!res.ok) {
78
+ // An empty list and a failed request are different things: a 401 here
79
+ // means "sign in", not "no runs yet".
80
+ return {
81
+ ok: false,
82
+ status: res.status,
83
+ data: fallback,
84
+ detail: await parse_error(res)
85
+ };
86
+ }
87
+ return { ok: true, status: res.status, data: pick(await res.json()) };
88
+ } catch (e) {
89
+ return { ok: false, status: 0, data: fallback, detail: String(e) };
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Create the bucket if it does not exist and confirm it is writable. Stores
95
+ * nothing server-side — the caller keeps its own choice and names it on every
96
+ * later request.
97
+ */
98
+ export async function connect_bucket(
99
+ root: string,
100
+ bucket_id: string
101
+ ): Promise<HistoryResult<null>> {
102
+ if (!is_valid_bucket_id(bucket_id)) {
103
+ return {
104
+ ok: false,
105
+ status: 422,
106
+ data: null,
107
+ detail: "invalid bucket id"
108
+ };
109
+ }
110
+ return request(
111
+ url(root, "connect"),
112
+ {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({ bucket_id })
116
+ },
117
+ null,
118
+ () => null
119
+ );
120
+ }
121
+
122
+ export async function list_user_buckets(
123
+ root: string
124
+ ): Promise<HistoryResult<string[]>> {
125
+ return request<string[]>(url(root, "buckets"), {}, [], (b) =>
126
+ Array.isArray(b?.buckets) ? b.buckets : []
127
+ );
128
+ }
129
+
130
+ export async function list_bucket_records(
131
+ root: string,
132
+ bucket: string
133
+ ): Promise<HistoryResult<HistoryRecord[]>> {
134
+ return request(url(root, "records", { bucket }), {}, [], (b) =>
135
+ Array.isArray(b?.records) ? b.records : []
136
+ );
137
+ }
138
+
139
+ export function asset_url(
140
+ root: string,
141
+ bucket: string,
142
+ endpoint: string,
143
+ record_id: string,
144
+ filename: string
145
+ ): string {
146
+ return url(
147
+ root,
148
+ `records/${encodeURIComponent(endpoint)}/${encodeURIComponent(
149
+ record_id
150
+ )}/assets/${encodeURIComponent(filename)}`,
151
+ { bucket }
152
+ );
153
+ }
@@ -11,6 +11,7 @@ import type { GradioEvent, StatusMessage } from "../types";
11
11
  const KEY_ROOT = "gradio:run-history:";
12
12
  const STORAGE_PREFIX = `${KEY_ROOT}v2:`;
13
13
  const REPLAY_PREFIX = `${KEY_ROOT}replay:v2:`;
14
+ const DESTINATION_PREFIX = `${KEY_ROOT}destination:v1:`;
14
15
  const MAX_RUNS = 100;
15
16
  const MAX_APPS = 8;
16
17
 
@@ -26,6 +27,10 @@ export interface RunHistoryScope {
26
27
  username?: string | null;
27
28
  }
28
29
 
30
+ export type RunHistoryStorage =
31
+ | { type: "browser"; bucket_id?: string }
32
+ | { type: "bucket"; bucket_id: string };
33
+
29
34
  /**
30
35
  * Run history is a side effect of submitting, never the point of it, so no
31
36
  * failure in here may propagate into the caller and break the app. Every
@@ -115,6 +120,45 @@ function replay_key(scope: RunHistoryScope | null | undefined): string | null {
115
120
  return key ? key.replace(STORAGE_PREFIX, REPLAY_PREFIX) : null;
116
121
  }
117
122
 
123
+ function destination_key(
124
+ scope: RunHistoryScope | null | undefined
125
+ ): string | null {
126
+ const key = storage_key(scope);
127
+ return key ? key.replace(STORAGE_PREFIX, DESTINATION_PREFIX) : null;
128
+ }
129
+
130
+ function read_run_history_storage_impl(
131
+ scope: RunHistoryScope | null | undefined
132
+ ): RunHistoryStorage {
133
+ const key = destination_key(scope);
134
+ if (!key) return { type: "browser" };
135
+ try {
136
+ const value = JSON.parse(window.localStorage.getItem(key) || "null");
137
+ if (typeof value?.bucket_id === "string") {
138
+ return value.type === "bucket"
139
+ ? { type: "bucket", bucket_id: value.bucket_id }
140
+ : { type: "browser", bucket_id: value.bucket_id };
141
+ }
142
+ return { type: "browser" };
143
+ } catch {
144
+ return { type: "browser" };
145
+ }
146
+ }
147
+
148
+ function set_run_history_storage_impl(
149
+ scope: RunHistoryScope | null | undefined,
150
+ storage: RunHistoryStorage
151
+ ): void {
152
+ const key = destination_key(scope);
153
+ if (!key) return;
154
+ if (storage.type === "browser" && !storage.bucket_id) {
155
+ window.localStorage.removeItem(key);
156
+ } else {
157
+ window.localStorage.setItem(key, JSON.stringify(storage));
158
+ }
159
+ notify_run_history_change();
160
+ }
161
+
118
162
  /** When a run was most recently saved under a key, for deciding what to drop. */
119
163
  function last_saved_at(key: string): number {
120
164
  try {
@@ -140,7 +184,10 @@ function prune_apps(current_key: string): void {
140
184
  const stale = [
141
185
  // Keys written by an older layout can never be read again.
142
186
  ...keys.filter(
143
- (key) => !key.startsWith(STORAGE_PREFIX) && !key.startsWith(REPLAY_PREFIX)
187
+ (key) =>
188
+ !key.startsWith(STORAGE_PREFIX) &&
189
+ !key.startsWith(REPLAY_PREFIX) &&
190
+ !key.startsWith(DESTINATION_PREFIX)
144
191
  ),
145
192
  ...keys
146
193
  .filter((key) => key.startsWith(STORAGE_PREFIX) && key !== current_key)
@@ -365,6 +412,7 @@ function apply_run_history_replay_impl(
365
412
  }
366
413
 
367
414
  function start_run_history_impl(options: StartRunOptions): string | null {
415
+ if (read_run_history_storage_impl(options).type === "bucket") return null;
368
416
  const key = storage_key(options);
369
417
  if (!key) return null;
370
418
 
@@ -522,6 +570,21 @@ export function read_run_history(
522
570
  return safely(() => read_run_history_impl(scope), []);
523
571
  }
524
572
 
573
+ export function read_run_history_storage(
574
+ scope: RunHistoryScope | null | undefined
575
+ ): RunHistoryStorage {
576
+ return safely(() => read_run_history_storage_impl(scope), {
577
+ type: "browser"
578
+ });
579
+ }
580
+
581
+ export function set_run_history_storage(
582
+ scope: RunHistoryScope | null | undefined,
583
+ storage: RunHistoryStorage
584
+ ): void {
585
+ safely(() => set_run_history_storage_impl(scope, storage), undefined);
586
+ }
587
+
525
588
  export function clear_run_history(
526
589
  scope: RunHistoryScope | null | undefined
527
590
  ): void {
@@ -10,7 +10,12 @@ import type {
10
10
  SubmitIterable
11
11
  } from "../types";
12
12
 
13
- import { skip_queue, post_message, handle_payload } from "../helpers/data";
13
+ import {
14
+ skip_queue,
15
+ post_message,
16
+ handle_payload,
17
+ sign_file_urls
18
+ } from "../helpers/data";
14
19
  import { get_zerogpu_origin } from "../helpers/zerogpu";
15
20
  import {
16
21
  handle_message,
@@ -30,6 +35,7 @@ import {
30
35
  import { apply_diff_stream, close_stream } from "./stream";
31
36
  import { Client } from "../client";
32
37
  import {
38
+ read_run_history_storage,
33
39
  start_run_history,
34
40
  update_run_history,
35
41
  update_run_inputs
@@ -63,12 +69,13 @@ export function submit(
63
69
  api_prefix
64
70
  } = this;
65
71
 
66
- const addt_headers = additional_headers || { "x-gradio-user": "api" };
72
+ const base_headers = additional_headers || { "x-gradio-user": "api" };
67
73
 
68
74
  const that = this;
69
75
 
70
76
  if (!api_info) throw new Error(NO_API_INFO_MSG);
71
77
  if (!config) throw new Error("Could not resolve app config");
78
+ const root = config.root;
72
79
 
73
80
  let { fn_index, endpoint_info, dependency } = get_endpoint_info(
74
81
  api_info,
@@ -114,6 +121,13 @@ export function submit(
114
121
  const history_enabled =
115
122
  config.run_history !== false && this.options.record_history !== false;
116
123
  const history_scope = { app_id: config.app_id, username: config.username };
124
+ const history_storage = read_run_history_storage(history_scope);
125
+ const addt_headers = {
126
+ ...base_headers,
127
+ ...(history_enabled && history_storage.type === "bucket"
128
+ ? { "x-gradio-history-bucket": history_storage.bucket_id }
129
+ : {})
130
+ };
117
131
  const history_run_id =
118
132
  !history_enabled || !is_documented_endpoint
119
133
  ? null
@@ -162,6 +176,9 @@ export function submit(
162
176
  // event subscription methods
163
177
  function fire_event(event: GradioEvent): void {
164
178
  update_run_history(history_scope, history_run_id, event);
179
+ if (event.type === "data" || event.type === "render") {
180
+ sign_file_urls(event.data, root, api_prefix, that.jwt);
181
+ }
165
182
  if (all_events || events_to_publish[event.type]) {
166
183
  push_event(event);
167
184
  }
@@ -385,7 +402,8 @@ export function submit(
385
402
  ...payload,
386
403
  session_hash,
387
404
  event_id
388
- }
405
+ },
406
+ addt_headers
389
407
  );
390
408
  if (status !== 200) {
391
409
  fire_event({
@@ -28,7 +28,10 @@ export async function view_api(this: Client): Promise<any> {
28
28
  if (typeof window !== "undefined" && window.gradio_api_info) {
29
29
  api_info = window.gradio_api_info;
30
30
  } else {
31
- const url = join_urls(config.root, this.api_prefix, API_INFO_URL);
31
+ const url = new URL(
32
+ join_urls(config.root, this.api_prefix, API_INFO_URL)
33
+ );
34
+ if (this.page !== null) url.searchParams.set("page", this.page);
32
35
  response = await this.fetch(url, {
33
36
  headers,
34
37
  credentials: this.options.credentials ?? "same-origin"