| 731 | if (!bundleUrl) return; |
| 732 | |
| 733 | const fetchBundle = async () => { |
| 734 | setLoading(true); |
| 735 | setError(null); |
| 736 | try { |
| 737 | const isBase64 = bundleUrl.endsWith('.base64') || bundleUrl.endsWith('.txt'); |
| 738 | setProgressText(isBase64 ? "Downloading and decoding bundle..." : "Downloading bundle..."); |
| 739 | setProgressValue(5); |
| 740 | |
| 741 | const response = await fetchWithFallbackProxies(bundleUrl, (loaded, total) => { |
| 742 | const mbLoaded = (loaded / 1024 / 1024).toFixed(2); |
| 743 | if (total > 0) { |
| 744 | const pct = Math.round((loaded / total) * 100); |
| 745 | setProgressText(`Downloading bundle: ${pct}% (${mbLoaded} MB)`); |
| 746 | setProgressValue(5 + Math.floor(pct * 0.45)); // maps 0-100% to 5-50% progress bar |
| 747 | } else { |
| 748 | setProgressText(`Downloading bundle: ${mbLoaded} MB...`); |
| 749 | } |
| 750 | }); |
| 751 | |
| 752 | if (!response.ok) { |
| 753 | throw new Error(`Failed to fetch bundle from URL (${response.status})`); |
| 754 | } |
| 755 | |
| 756 | setProgressText(isBase64 ? "Decoding bundle data..." : "Unzipping bundle in-memory..."); |
| 757 | setProgressValue(55); |
| 758 | |
| 759 | let buffer: ArrayBuffer; |
| 760 | if (isBase64) { |
| 761 | const base64Text = await response.text(); |
| 762 | const binaryString = atob(base64Text.trim()); |
| 763 | const len = binaryString.length; |
| 764 | const bytes = new Uint8Array(len); |
| 765 | for (let i = 0; i < len; i++) { |
| 766 | bytes[i] = binaryString.charCodeAt(i); |
| 767 | } |
| 768 | buffer = bytes.buffer; |
| 769 | } else { |
| 770 | buffer = await response.arrayBuffer(); |
| 771 | } |
| 772 | |
| 773 | setProgressText("Unzipping bundle in-memory..."); |
| 774 | setProgressValue(60); |
| 775 | const jszip = await JSZip.loadAsync(buffer); |
| 776 | |
| 777 | const nodesFile = jszip.file("nodes.jsonl"); |
| 778 | const edgesFile = jszip.file("edges.jsonl"); |
| 779 | |
| 780 | if (!nodesFile || !edgesFile) { |
| 781 | throw new Error("Invalid CGC bundle: nodes.jsonl and edges.jsonl are required."); |
| 782 | } |
| 783 | |
| 784 | let metadata: any = {}; |
| 785 | if (jszip.file("metadata.json")) { |
| 786 | const metaText = await jszip.file("metadata.json")!.async("text"); |
| 787 | try { |
| 788 | metadata = JSON.parse(metaText); |
| 789 | } catch (e) { |
| 790 | console.warn("Could not parse metadata.json", e); |