@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.
package/dist/index.js CHANGED
@@ -62,9 +62,11 @@ function map_names_to_ids(fns) {
62
62
  });
63
63
  return apis;
64
64
  }
65
- function resolve_config_root(root, current_location) {
66
- const root_url = new URL(root, current_location);
65
+ function resolve_config_root(root, current_location, prioritize_current_location = false) {
67
66
  const current_url = new URL(current_location);
67
+ const root_url = new URL(root, current_url);
68
+ const is_colab_runtime = root_url.hostname.endsWith(".colab-user-runtimes.internal") || root_url.hostname.endsWith(".codatalab-user-runtimes.internal");
69
+ if (prioritize_current_location || is_colab_runtime) return new URL(new URL(root, current_url).pathname, current_url).toString().replace(/\/$/, "");
68
70
  if (root_url.hostname !== current_url.hostname) return root;
69
71
  root_url.protocol = current_url.protocol;
70
72
  root_url.host = current_url.host;
@@ -75,7 +77,9 @@ async function resolve_config(endpoint, strip_current_page = true) {
75
77
  if (typeof window !== "undefined" && window.gradio_config && location.origin !== "http://localhost:9876") {
76
78
  if (strip_current_page && window.gradio_config.current_page) endpoint = endpoint.substring(0, endpoint.lastIndexOf("/"));
77
79
  if (window.gradio_config.dev_mode || typeof window !== "undefined" && window?.BUILD_MODE === "dev") {
78
- let config_url = join_urls(endpoint, this.deep_link ? CONFIG_URL + "?deep_link=" + this.deep_link : CONFIG_URL);
80
+ let config_url = new URL(join_urls(endpoint, CONFIG_URL));
81
+ if (this.deep_link) config_url.searchParams.set("deep_link", this.deep_link);
82
+ if (this.page !== null) config_url.searchParams.set("page", this.page);
79
83
  const config = await handleConfigResponse(await this.fetch(config_url, {
80
84
  headers,
81
85
  credentials: this.options.credentials ?? "same-origin"
@@ -87,10 +91,12 @@ async function resolve_config(endpoint, strip_current_page = true) {
87
91
  };
88
92
  }
89
93
  const config = { ...window.gradio_config };
90
- config.root = resolve_config_root(config.root, location.href);
94
+ config.root = resolve_config_root(config.root, location.href, config.is_colab);
91
95
  return config;
92
96
  } else if (endpoint) {
93
- let config_url = join_urls(endpoint, this.deep_link ? CONFIG_URL + "?deep_link=" + this.deep_link : CONFIG_URL);
97
+ let config_url = new URL(join_urls(endpoint, CONFIG_URL));
98
+ if (this.deep_link) config_url.searchParams.set("deep_link", this.deep_link);
99
+ if (this.page !== null) config_url.searchParams.set("page", this.page);
94
100
  const config = await handleConfigResponse(await this.fetch(config_url, {
95
101
  headers,
96
102
  credentials: this.options.credentials ?? "same-origin"
@@ -489,7 +495,8 @@ async function view_api() {
489
495
  let api_info;
490
496
  if (typeof window !== "undefined" && window.gradio_api_info) api_info = window.gradio_api_info;
491
497
  else {
492
- const url = join_urls(config.root, this.api_prefix, API_INFO_URL);
498
+ const url = new URL(join_urls(config.root, this.api_prefix, API_INFO_URL));
499
+ if (this.page !== null) url.searchParams.set("page", this.page);
493
500
  response = await this.fetch(url, {
494
501
  headers,
495
502
  credentials: this.options.credentials ?? "same-origin"
@@ -609,10 +616,11 @@ async function upload(file_data, root_url, upload_id, max_file_size) {
609
616
  if (response.error) throw new Error(response.error);
610
617
  else {
611
618
  if (response.files) return response.files.map((f, i) => {
619
+ const encoded_path = f.split("/").map((segment) => encodeURIComponent(segment)).join("/");
612
620
  return new FileData({
613
621
  ...file_data[i],
614
622
  path: f,
615
- url: `${root_url}${this.api_prefix}/file=${f}`
623
+ url: `${root_url}${this.api_prefix}/file=${encoded_path}`
616
624
  });
617
625
  });
618
626
  return [];
@@ -774,6 +782,48 @@ function handle_payload(resolved_payload, dependency, components, type, with_nul
774
782
  }
775
783
  return updated_payload;
776
784
  }
785
+ var FILE_ROUTE_PREFIXES = [
786
+ "/file=",
787
+ "/file/",
788
+ "/stream/",
789
+ "/proxy="
790
+ ];
791
+ /**
792
+ * Add the Space JWT required for browser-managed media requests.
793
+ *
794
+ * This is a bearer credential and must not be logged or persisted. Restrict it
795
+ * to Gradio routes that serve files; the user's Hugging Face token remains in
796
+ * the Authorization header used to obtain this credential.
797
+ */
798
+ function sign_file_urls(data, root, api_prefix, jwt) {
799
+ if (!jwt || data === null || typeof data !== "object") return;
800
+ const signature = jwt;
801
+ const root_url = new URL(root);
802
+ const root_path = root_url.pathname.replace(/\/+$/, "");
803
+ const normalized_api_prefix = api_prefix ? `/${api_prefix.replace(/^\/+|\/+$/g, "")}` : "";
804
+ const route_bases = new Set([`${root_path}${normalized_api_prefix}`, normalized_api_prefix]);
805
+ if (!normalized_api_prefix) {
806
+ route_bases.add(`${root_path}/gradio_api`);
807
+ route_bases.add("/gradio_api");
808
+ }
809
+ function visit(value) {
810
+ if (value === null || typeof value !== "object") return;
811
+ const object = value;
812
+ if (object.meta?._type === "gradio.FileData" && typeof object.url === "string") {
813
+ const file_url = new URL(object.url, `${root_url.toString().replace(/\/$/, "")}/`);
814
+ const is_file_route = [...route_bases].some((base) => FILE_ROUTE_PREFIXES.some((route) => file_url.pathname.startsWith(`${base}${route}`)));
815
+ if ((file_url.protocol === "http:" || file_url.protocol === "https:") && file_url.origin === root_url.origin && is_file_route) {
816
+ file_url.searchParams.set("__sign", signature);
817
+ object.url = file_url.toString();
818
+ }
819
+ }
820
+ for (const child of Object.values(object)) visit(child);
821
+ }
822
+ visit(data);
823
+ }
824
+ function sign_config_file_urls(config, jwt) {
825
+ for (const component of config.components || []) sign_file_urls(component.props?.value, config.root, config.api_prefix || "", jwt);
826
+ }
777
827
  //#endregion
778
828
  //#region src/utils/handle_blob.ts
779
829
  async function handle_blob(endpoint, data, api_info) {
@@ -1432,6 +1482,7 @@ function readable_stream(input, init = {}) {
1432
1482
  var KEY_ROOT = "gradio:run-history:";
1433
1483
  var STORAGE_PREFIX = `${KEY_ROOT}v2:`;
1434
1484
  var REPLAY_PREFIX = `${KEY_ROOT}replay:v2:`;
1485
+ var DESTINATION_PREFIX = `${KEY_ROOT}destination:v1:`;
1435
1486
  var MAX_RUNS = 100;
1436
1487
  var MAX_APPS = 8;
1437
1488
  /**
@@ -1469,6 +1520,34 @@ function replay_key(scope) {
1469
1520
  const key = storage_key(scope);
1470
1521
  return key ? key.replace(STORAGE_PREFIX, REPLAY_PREFIX) : null;
1471
1522
  }
1523
+ function destination_key(scope) {
1524
+ const key = storage_key(scope);
1525
+ return key ? key.replace(STORAGE_PREFIX, DESTINATION_PREFIX) : null;
1526
+ }
1527
+ function read_run_history_storage_impl(scope) {
1528
+ const key = destination_key(scope);
1529
+ if (!key) return { type: "browser" };
1530
+ try {
1531
+ const value = JSON.parse(window.localStorage.getItem(key) || "null");
1532
+ if (typeof value?.bucket_id === "string") return value.type === "bucket" ? {
1533
+ type: "bucket",
1534
+ bucket_id: value.bucket_id
1535
+ } : {
1536
+ type: "browser",
1537
+ bucket_id: value.bucket_id
1538
+ };
1539
+ return { type: "browser" };
1540
+ } catch {
1541
+ return { type: "browser" };
1542
+ }
1543
+ }
1544
+ function set_run_history_storage_impl(scope, storage) {
1545
+ const key = destination_key(scope);
1546
+ if (!key) return;
1547
+ if (storage.type === "browser" && !storage.bucket_id) window.localStorage.removeItem(key);
1548
+ else window.localStorage.setItem(key, JSON.stringify(storage));
1549
+ notify_run_history_change();
1550
+ }
1472
1551
  /** When a run was most recently saved under a key, for deciding what to drop. */
1473
1552
  function last_saved_at(key) {
1474
1553
  try {
@@ -1485,7 +1564,7 @@ function last_saved_at(key) {
1485
1564
  */
1486
1565
  function prune_apps(current_key) {
1487
1566
  const keys = Object.keys(window.localStorage).filter((key) => key.startsWith(KEY_ROOT));
1488
- const stale = [...keys.filter((key) => !key.startsWith(STORAGE_PREFIX) && !key.startsWith(REPLAY_PREFIX)), ...keys.filter((key) => key.startsWith(STORAGE_PREFIX) && key !== current_key).sort((a, b) => last_saved_at(b) - last_saved_at(a)).slice(MAX_APPS - 1)];
1567
+ const stale = [...keys.filter((key) => !key.startsWith(STORAGE_PREFIX) && !key.startsWith(REPLAY_PREFIX) && !key.startsWith(DESTINATION_PREFIX)), ...keys.filter((key) => key.startsWith(STORAGE_PREFIX) && key !== current_key).sort((a, b) => last_saved_at(b) - last_saved_at(a)).slice(MAX_APPS - 1)];
1489
1568
  for (const key of stale) try {
1490
1569
  window.localStorage.removeItem(key);
1491
1570
  window.sessionStorage.removeItem(key.replace(STORAGE_PREFIX, REPLAY_PREFIX));
@@ -1615,6 +1694,7 @@ function apply_run_history_replay_impl(config) {
1615
1694
  return run ? restore_run_impl(config, run) : false;
1616
1695
  }
1617
1696
  function start_run_history_impl(options) {
1697
+ if (read_run_history_storage_impl(options).type === "bucket") return null;
1618
1698
  const key = storage_key(options);
1619
1699
  if (!key) return null;
1620
1700
  prune_apps(key);
@@ -1694,6 +1774,12 @@ function mark_complete(run, event) {
1694
1774
  function read_run_history(scope) {
1695
1775
  return safely(() => read_run_history_impl(scope), []);
1696
1776
  }
1777
+ function read_run_history_storage(scope) {
1778
+ return safely(() => read_run_history_storage_impl(scope), { type: "browser" });
1779
+ }
1780
+ function set_run_history_storage(scope, storage) {
1781
+ safely(() => set_run_history_storage_impl(scope, storage), void 0);
1782
+ }
1697
1783
  function clear_run_history(scope) {
1698
1784
  safely(() => clear_run_history_impl(scope), void 0);
1699
1785
  }
@@ -1734,10 +1820,11 @@ function submit(endpoint, data = {}, event_data, trigger_id, all_events, additio
1734
1820
  try {
1735
1821
  const { token } = this.options;
1736
1822
  const { fetch, app_reference, config, session_hash, api_info, api_map, stream_status, pending_stream_messages, pending_diff_streams, event_callbacks, unclosed_events, post_data, options, api_prefix } = this;
1737
- const addt_headers = additional_headers || { "x-gradio-user": "api" };
1823
+ const base_headers = additional_headers || { "x-gradio-user": "api" };
1738
1824
  const that = this;
1739
1825
  if (!api_info) throw new Error(NO_API_INFO_MSG);
1740
1826
  if (!config) throw new Error("Could not resolve app config");
1827
+ const root = config.root;
1741
1828
  let { fn_index, endpoint_info, dependency } = get_endpoint_info(api_info, endpoint, api_map, config);
1742
1829
  let resolved_data = map_data_to_params(data, endpoint_info);
1743
1830
  let protocol = config.protocol ?? "ws";
@@ -1759,6 +1846,11 @@ function submit(endpoint, data = {}, event_data, trigger_id, all_events, additio
1759
1846
  app_id: config.app_id,
1760
1847
  username: config.username
1761
1848
  };
1849
+ const history_storage = read_run_history_storage(history_scope);
1850
+ const addt_headers = {
1851
+ ...base_headers,
1852
+ ...history_enabled && history_storage.type === "bucket" ? { "x-gradio-history-bucket": history_storage.bucket_id } : {}
1853
+ };
1762
1854
  const history_run_id = !history_enabled || !is_documented_endpoint ? null : start_run_history({
1763
1855
  ...history_scope,
1764
1856
  endpoint: history_endpoint,
@@ -1782,6 +1874,7 @@ function submit(endpoint, data = {}, event_data, trigger_id, all_events, additio
1782
1874
  }, {}) || {};
1783
1875
  function fire_event(event) {
1784
1876
  update_run_history(history_scope, history_run_id, event);
1877
+ if (event.type === "data" || event.type === "render") sign_file_urls(event.data, root, api_prefix, that.jwt);
1785
1878
  if (all_events || events_to_publish[event.type]) push_event(event);
1786
1879
  }
1787
1880
  async function cancel() {
@@ -1932,7 +2025,7 @@ function submit(endpoint, data = {}, event_data, trigger_id, all_events, additio
1932
2025
  ...payload,
1933
2026
  session_hash,
1934
2027
  event_id
1935
- });
2028
+ }, addt_headers);
1936
2029
  if (status !== 200) {
1937
2030
  fire_event({
1938
2031
  type: "status",
@@ -2285,6 +2378,7 @@ var Client = class {
2285
2378
  app_reference;
2286
2379
  options;
2287
2380
  deep_link = null;
2381
+ page = null;
2288
2382
  config;
2289
2383
  api_prefix = "";
2290
2384
  api_info;
@@ -2365,6 +2459,7 @@ var Client = class {
2365
2459
  constructor(app_reference, options = { events: ["data"] }) {
2366
2460
  this.app_reference = app_reference;
2367
2461
  this.deep_link = options.query_params?.deep_link || null;
2462
+ this.page = options.query_params?.page ?? null;
2368
2463
  if (!options.events) options.events = ["data"];
2369
2464
  normalise_token_option(options);
2370
2465
  this.options = options;
@@ -2396,14 +2491,10 @@ var Client = class {
2396
2491
  this.api_map = map_names_to_ids(this.config?.dependencies || []);
2397
2492
  }
2398
2493
  async _resolve_heartbeat(_config) {
2399
- if (_config) {
2400
- this.config = _config;
2401
- this.api_prefix = _config.api_prefix || "";
2402
- if (this.config && this.config.connect_heartbeat) {
2403
- if (this.config.space_id && this.options.token) this.jwt = await get_jwt(this.config.space_id, this.options.token, this.cookies);
2404
- }
2405
- }
2406
- if (_config.space_id && this.options.token) this.jwt = await get_jwt(_config.space_id, this.options.token);
2494
+ this.config = _config;
2495
+ this.api_prefix = _config.api_prefix || "";
2496
+ if (_config.space_id && this.options.token) this.jwt = await get_jwt(_config.space_id, this.options.token, this.cookies);
2497
+ sign_config_file_urls(this.config, this.jwt);
2407
2498
  if (this.config && this.config.connect_heartbeat) {
2408
2499
  const heartbeat_url = new URL(`${this.config.root}${this.api_prefix}/${HEARTBEAT_URL}/${this.session_hash}`);
2409
2500
  if (this.jwt) heartbeat_url.searchParams.set("__sign", this.jwt);
@@ -2444,6 +2535,7 @@ var Client = class {
2444
2535
  this.config = config;
2445
2536
  this.api_prefix = config.api_prefix || "";
2446
2537
  this.api_map = map_names_to_ids(config.dependencies || []);
2538
+ sign_config_file_urls(this.config, this.jwt);
2447
2539
  try {
2448
2540
  this.api_info = await this.view_api();
2449
2541
  } catch (e) {
@@ -2553,7 +2645,9 @@ var Client = class {
2553
2645
  credentials: this.options.credentials ?? "same-origin"
2554
2646
  });
2555
2647
  if (!response.ok) throw new Error("Could not connect to component server: " + response.statusText);
2556
- return await response.json();
2648
+ const output = await response.json();
2649
+ sign_file_urls(output, this.config.root, this.config.api_prefix || "", this.jwt);
2650
+ return output;
2557
2651
  } catch (e) {
2558
2652
  console.warn(e);
2559
2653
  }
@@ -2594,4 +2688,91 @@ async function duplicate_space(app_reference, options) {
2594
2688
  return await Client.duplicate(app_reference, options);
2595
2689
  }
2596
2690
  //#endregion
2597
- export { Client, FileData, MISSING_CREDENTIALS_MSG, apply_run_history_replay, clear_run_history, client, consume_run_history_replay, delete_run_history, duplicate_space as duplicate, handle_file, on_run_history_change, predict, prepare_files, read_run_history, run_history_url, stage_run_history_replay, submit, upload, upload_files };
2691
+ //#region src/utils/bucket_sync.ts
2692
+ /**
2693
+ * Client for the durable run history in an HF Hub bucket
2694
+ * (`/gradio_api/run-history/*`).
2695
+ *
2696
+ * Read-only by design. Records are written by the server from the run it
2697
+ * actually executed — there is no push function here, and nothing in this
2698
+ * module can create a record.
2699
+ *
2700
+ * Every call names the bucket it is talking about. The server holds no
2701
+ * per-session binding, so two tabs (or two apps on one origin) cannot end up
2702
+ * reading each other's history.
2703
+ */
2704
+ var BUCKET_ID_RE = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-][a-zA-Z0-9_./-]*$/;
2705
+ function is_valid_bucket_id(id) {
2706
+ if (!BUCKET_ID_RE.test(id)) return false;
2707
+ return !id.split("/").some((seg) => seg === "" || seg === "." || seg === "..");
2708
+ }
2709
+ function url(root, path, params) {
2710
+ const query = params ? `?${new URLSearchParams(params)}` : "";
2711
+ const base = root.replace(/\/+$/, "");
2712
+ if (base) return `${base}/gradio_api/run-history/${path}${query}`;
2713
+ if (typeof document !== "undefined" && document.baseURI) return new URL(`gradio_api/run-history/${path}${query}`, document.baseURI).toString();
2714
+ return `gradio_api/run-history/${path}${query}`;
2715
+ }
2716
+ async function parse_error(res) {
2717
+ try {
2718
+ const j = await res.json();
2719
+ return typeof j?.detail === "string" ? j.detail : `${res.status}`;
2720
+ } catch {
2721
+ return `${res.status}`;
2722
+ }
2723
+ }
2724
+ async function request(input, init, fallback, pick) {
2725
+ try {
2726
+ const res = await fetch(input, {
2727
+ credentials: "include",
2728
+ ...init
2729
+ });
2730
+ if (!res.ok) return {
2731
+ ok: false,
2732
+ status: res.status,
2733
+ data: fallback,
2734
+ detail: await parse_error(res)
2735
+ };
2736
+ return {
2737
+ ok: true,
2738
+ status: res.status,
2739
+ data: pick(await res.json())
2740
+ };
2741
+ } catch (e) {
2742
+ return {
2743
+ ok: false,
2744
+ status: 0,
2745
+ data: fallback,
2746
+ detail: String(e)
2747
+ };
2748
+ }
2749
+ }
2750
+ /**
2751
+ * Create the bucket if it does not exist and confirm it is writable. Stores
2752
+ * nothing server-side — the caller keeps its own choice and names it on every
2753
+ * later request.
2754
+ */
2755
+ async function connect_bucket(root, bucket_id) {
2756
+ if (!is_valid_bucket_id(bucket_id)) return {
2757
+ ok: false,
2758
+ status: 422,
2759
+ data: null,
2760
+ detail: "invalid bucket id"
2761
+ };
2762
+ return request(url(root, "connect"), {
2763
+ method: "POST",
2764
+ headers: { "Content-Type": "application/json" },
2765
+ body: JSON.stringify({ bucket_id })
2766
+ }, null, () => null);
2767
+ }
2768
+ async function list_user_buckets(root) {
2769
+ return request(url(root, "buckets"), {}, [], (b) => Array.isArray(b?.buckets) ? b.buckets : []);
2770
+ }
2771
+ async function list_bucket_records(root, bucket) {
2772
+ return request(url(root, "records", { bucket }), {}, [], (b) => Array.isArray(b?.records) ? b.records : []);
2773
+ }
2774
+ function asset_url(root, bucket, endpoint, record_id, filename) {
2775
+ return url(root, `records/${encodeURIComponent(endpoint)}/${encodeURIComponent(record_id)}/assets/${encodeURIComponent(filename)}`, { bucket });
2776
+ }
2777
+ //#endregion
2778
+ export { Client, FileData, MISSING_CREDENTIALS_MSG, apply_run_history_replay, asset_url, clear_run_history, client, connect_bucket, consume_run_history_replay, delete_run_history, duplicate_space as duplicate, handle_file, is_valid_bucket_id, list_bucket_records, list_user_buckets, on_run_history_change, predict, prepare_files, read_run_history, read_run_history_storage, run_history_url, set_run_history_storage, stage_run_history_replay, submit, upload, upload_files };