Spaces:
Running
Running
| /** | |
| * GuidedTour – Multilingual, versioned guided tour using Driver.js. | |
| * Shares language preference with HelpTooltip (localStorage key: onnx_explorer_help_lang). | |
| * Auto-start shows only NEW steps; button click shows ALL steps. | |
| * Requirements: 29.1–29.8 | |
| */ | |
| (function () { | |
| 'use strict'; | |
| // ─── Current tour version ───────────────────────────────────────────────── | |
| var CURRENT_VERSION = 3; | |
| // ─── Shared language config ─────────────────────────────────────────────── | |
| var LANG_STORAGE_KEY = 'onnx_explorer_help_lang'; | |
| var DEFAULT_LANG = 'en'; | |
| var SUPPORTED_LANGS = ['en', 'vi', 'ja']; | |
| // ─── UI strings per language ────────────────────────────────────────────── | |
| var UI_STRINGS = { | |
| en: { next: 'Next →', prev: '← Back', done: 'Done ✓', btnLabel: 'Guide' }, | |
| vi: { next: 'Tiếp theo →', prev: '← Quay lại', done: 'Hoàn thành ✓', btnLabel: 'Hướng dẫn' }, | |
| ja: { next: '次へ →', prev: '← 戻る', done: '完了 ✓', btnLabel: 'ガイド' } | |
| }; | |
| // ─── Tour steps (multilingual, organised by version) ────────────────────── | |
| var STEPS_V1 = [ | |
| { | |
| element: '#modelListContainer', | |
| popover: { | |
| title: { en: 'Model List', vi: 'Danh Sách Mô Hình', ja: 'モデル一覧' }, | |
| description: { | |
| en: 'Available ONNX models are listed here. Click a model to explore its details.', | |
| vi: 'Đây là danh sách các mô hình ONNX có sẵn. Click vào một mô hình để xem chi tiết.', | |
| ja: '利用可能なONNXモデルの一覧です。モデルをクリックして詳細を確認できます。' | |
| }, | |
| side: 'right', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#searchInput', | |
| popover: { | |
| title: { en: 'Search Models', vi: 'Tìm Kiếm Mô Hình', ja: 'モデル検索' }, | |
| description: { | |
| en: 'Type a model name to quickly filter the list.', | |
| vi: 'Nhập tên mô hình để tìm kiếm nhanh trong danh sách.', | |
| ja: 'モデル名を入力してリストをすばやくフィルタリングします。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#uploadBtn', | |
| popover: { | |
| title: { en: 'Upload Model', vi: 'Tải Lên Mô Hình', ja: 'モデルをアップロード' }, | |
| description: { | |
| en: 'Click to upload an ONNX file from your computer.', | |
| vi: 'Click để tải lên tệp ONNX từ máy tính của bạn.', | |
| ja: 'クリックしてコンピュータからONNXファイルをアップロードします。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#metadataContainer', | |
| popover: { | |
| title: { en: 'Metadata', vi: 'Siêu Dữ Liệu', ja: 'メタデータ' }, | |
| description: { | |
| en: 'General model info: producer name, version, opset and IR version.', | |
| vi: 'Thông tin chung về mô hình: tên nhà sản xuất, phiên bản, opset và IR version.', | |
| ja: 'モデルの一般情報:プロデューサー名、バージョン、opsetおよびIRバージョン。' | |
| }, | |
| side: 'left', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#inputOutputContainer', | |
| popover: { | |
| title: { en: 'Inputs & Outputs', vi: 'Đầu Vào & Đầu Ra', ja: '入力と出力' }, | |
| description: { | |
| en: 'Model input and output tensors with name, shape and data type.', | |
| vi: 'Các tensor đầu vào và đầu ra của mô hình với tên, shape và kiểu dữ liệu.', | |
| ja: 'モデルの入出力テンソル(名前、形状、データ型)を表示します。' | |
| }, | |
| side: 'left', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#initializerContainer', | |
| popover: { | |
| title: { en: 'Initializers', vi: 'Bộ Khởi Tạo', ja: '初期化子' }, | |
| description: { | |
| en: 'Constant tensors (weights, biases) embedded in the model.', | |
| vi: 'Danh sách các tensor hằng số (trọng số, bias) được nhúng trong mô hình.', | |
| ja: 'モデルに埋め込まれた定数テンソル(重み、バイアス)の一覧です。' | |
| }, | |
| side: 'top', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#graphContainer', | |
| popover: { | |
| title: { en: 'Model Graph', vi: 'Đồ Thị Mô Hình', ja: 'モデルグラフ' }, | |
| description: { | |
| en: 'Visualises the computation graph. Zoom, pan and click nodes to explore.', | |
| vi: 'Trực quan hóa đồ thị tính toán. Zoom, pan và click vào node để khám phá.', | |
| ja: '計算グラフを可視化します。ズーム、パン、ノードクリックで探索できます。' | |
| }, | |
| side: 'top', align: 'center' | |
| } | |
| }, | |
| { | |
| element: '#nodeDetailContainer', | |
| popover: { | |
| title: { en: 'Node Details', vi: 'Chi Tiết Node', ja: 'ノード詳細' }, | |
| description: { | |
| en: 'Click a node in the graph to see its full details here.', | |
| vi: 'Click vào một node trong đồ thị để xem thông tin chi tiết ở đây.', | |
| ja: 'グラフ内のノードをクリックすると、ここに詳細が表示されます。' | |
| }, | |
| side: 'left', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#exportBtn', | |
| popover: { | |
| title: { en: 'Export Info', vi: 'Xuất Thông Tin', ja: '情報をエクスポート' }, | |
| description: { | |
| en: 'Export all model information as a JSON file.', | |
| vi: 'Xuất toàn bộ thông tin mô hình dưới dạng tệp JSON.', | |
| ja: 'モデル情報をJSONファイルとしてエクスポートします。' | |
| }, | |
| side: 'bottom', align: 'end' | |
| } | |
| }, | |
| { | |
| element: '.help-icon', | |
| popover: { | |
| title: { en: 'Help Icons', vi: 'Icon Trợ Giúp', ja: 'ヘルプアイコン' }, | |
| description: { | |
| en: 'Click the (?) icon next to any section header for a multilingual explanation.', | |
| vi: 'Click vào icon (?) bên cạnh mỗi đầu mục để xem giải thích bằng nhiều ngôn ngữ.', | |
| ja: '各セクションヘッダーの(?)アイコンをクリックすると、多言語の説明が表示されます。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| } | |
| ]; | |
| var STEPS_V2 = [ | |
| { | |
| element: '.graph-search-container', | |
| popover: { | |
| title: { en: '🔍 Graph Node Search', vi: '🔍 Tìm Kiếm Node Trong Đồ Thị', ja: '🔍 グラフノード検索' }, | |
| description: { | |
| en: 'Search nodes by name or opType. Use ▲▼ to navigate results — the graph auto-zooms to each match.', | |
| vi: 'Tìm node theo tên hoặc opType. Dùng nút ▲▼ để duyệt kết quả, đồ thị tự động zoom đến node tìm thấy.', | |
| ja: '名前またはopTypeでノードを検索します。▲▼で結果を移動し、グラフが自動ズームします。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#graphLayoutBtn', | |
| popover: { | |
| title: { en: '📐 Graph Layout', vi: '📐 Chuyển Đổi Layout Đồ Thị', ja: '📐 グラフレイアウト' }, | |
| description: { | |
| en: 'Switch between Top-Down, Left-Right, Circle or Force-Directed layouts. Your choice is saved.', | |
| vi: 'Chọn bố cục: Top-Down, Left-Right, Circle hoặc Force-Directed. Lựa chọn được lưu lại.', | |
| ja: 'Top-Down、Left-Right、Circle、Force-Directedレイアウトを切り替えます。選択は保存されます。' | |
| }, | |
| side: 'bottom', align: 'end' | |
| } | |
| }, | |
| { | |
| element: '#nodeGroupingBtn', | |
| popover: { | |
| title: { en: '📦 Node Grouping', vi: '📦 Gom Nhóm Node', ja: '📦 ノードグループ化' }, | |
| description: { | |
| en: 'Group nodes by operator type to simplify large graphs. Click a group to expand/collapse.', | |
| vi: 'Gom các node cùng opType thành nhóm để đơn giản hóa đồ thị lớn. Click nhóm để mở rộng/thu gọn.', | |
| ja: 'オペレータタイプでノードをグループ化し、大きなグラフを簡素化します。グループをクリックで展開/折りたたみ。' | |
| }, | |
| side: 'bottom', align: 'end' | |
| } | |
| }, | |
| { | |
| element: '#nodeDetailContainer', | |
| popover: { | |
| title: { en: '📝 Node Annotations', vi: '📝 Ghi Chú Node', ja: '📝 ノード注釈' }, | |
| description: { | |
| en: 'Right-click a node or use the "Annotate" button in the detail panel to add notes. Annotations are saved per model.', | |
| vi: 'Click phải vào node hoặc dùng nút "Annotate" trong panel chi tiết để thêm ghi chú. Ghi chú được lưu theo từng mô hình.', | |
| ja: 'ノードを右クリックするか、詳細パネルの「Annotate」ボタンで注釈を追加します。注釈はモデルごとに保存されます。' | |
| }, | |
| side: 'left', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#flopsEstimatorContainer', | |
| popover: { | |
| title: { en: '⚡ FLOPs Estimation', vi: '⚡ Ước Tính FLOPs', ja: '⚡ FLOPs推定' }, | |
| description: { | |
| en: 'Estimated computational cost (FLOPs) for the entire model, broken down by operator type.', | |
| vi: 'Ước tính chi phí tính toán (FLOPs) cho toàn bộ mô hình, phân tích theo từng loại toán tử.', | |
| ja: 'モデル全体の推定計算コスト(FLOPs)をオペレータタイプ別に表示します。' | |
| }, | |
| side: 'top', align: 'start' | |
| } | |
| }, | |
| { | |
| element: '#langSwitcherBtn', | |
| popover: { | |
| title: { en: '🌐 Language', vi: '🌐 Ngôn Ngữ', ja: '🌐 言語' }, | |
| description: { | |
| en: 'Switch the interface language between English, Vietnamese and Japanese. Applies to help tooltips and guided tour.', | |
| vi: 'Chuyển đổi ngôn ngữ giao diện giữa Tiếng Anh, Tiếng Việt và Tiếng Nhật. Áp dụng cho tooltip trợ giúp và hướng dẫn.', | |
| ja: 'インターフェース言語を英語、ベトナム語、日本語に切り替えます。ヘルプツールチップとガイドツアーに適用されます。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| } | |
| ]; | |
| var STEPS_V3 = [ | |
| { | |
| element: '#uploadBtn', | |
| popover: { | |
| title: { en: '📂 SafeTensors Support', vi: '📂 Hỗ Trợ SafeTensors', ja: '📂 SafeTensorsサポート' }, | |
| description: { | |
| en: 'You can now upload .safetensors files in addition to .onnx files. The viewer displays tensor list, metadata and summary.', | |
| vi: 'Bạn có thể tải lên tệp .safetensors ngoài tệp .onnx. Trình xem hiển thị danh sách tensor, metadata và tổng quan.', | |
| ja: '.onnxファイルに加えて.safetensorsファイルもアップロードできます。ビューアはテンソル一覧、メタデータ、サマリーを表示します。' | |
| }, | |
| side: 'bottom', align: 'start' | |
| } | |
| } | |
| ]; | |
| // ─── Version registry ───────────────────────────────────────────────────── | |
| var VERSION_MAP = { 1: STEPS_V1, 2: STEPS_V2, 3: STEPS_V3 }; | |
| // ─── Language helpers ───────────────────────────────────────────────────── | |
| function _getLang() { | |
| try { | |
| // Share language with HelpTooltip | |
| var lang = localStorage.getItem(LANG_STORAGE_KEY); | |
| if (lang && SUPPORTED_LANGS.indexOf(lang) !== -1) return lang; | |
| } catch (_) { /* ignore */ } | |
| return DEFAULT_LANG; | |
| } | |
| /** Resolve a multilingual value: if it's an object with lang keys, pick current lang. */ | |
| function _t(val) { | |
| if (val && typeof val === 'object' && !Array.isArray(val)) { | |
| var lang = _getLang(); | |
| return val[lang] || val[DEFAULT_LANG] || val.en || ''; | |
| } | |
| return val || ''; | |
| } | |
| /** Convert multilingual step definitions into Driver.js-ready steps for current language. */ | |
| function _localizeSteps(steps) { | |
| return steps.map(function (step) { | |
| var pop = step.popover || {}; | |
| return { | |
| element: step.element, | |
| popover: { | |
| title: _t(pop.title), | |
| description: _t(pop.description), | |
| side: pop.side, | |
| align: pop.align | |
| } | |
| }; | |
| }); | |
| } | |
| // ─── Step collection helpers ────────────────────────────────────────────── | |
| function _getAllSteps() { | |
| var all = []; | |
| for (var v = 1; v <= CURRENT_VERSION; v++) { | |
| if (VERSION_MAP[v]) all = all.concat(VERSION_MAP[v]); | |
| } | |
| return all; | |
| } | |
| function _getNewSteps(completedVersion) { | |
| var steps = []; | |
| for (var v = completedVersion + 1; v <= CURRENT_VERSION; v++) { | |
| if (VERSION_MAP[v]) steps = steps.concat(VERSION_MAP[v]); | |
| } | |
| return steps; | |
| } | |
| // ─── GuidedTour class ───────────────────────────────────────────────────── | |
| function GuidedTour(options) { | |
| options = options || {}; | |
| this._storageKey = options.storageKey || 'onnx_explorer_tour_version'; | |
| this._driverObj = null; | |
| this._tourBtn = null; | |
| } | |
| GuidedTour.prototype.init = function () { | |
| if (!window.driver) { | |
| console.warn('[GuidedTour] Driver.js not loaded – tour unavailable.'); | |
| return; | |
| } | |
| this._createTourButton(); | |
| var completed = this._getCompletedVersion(); | |
| if (completed < CURRENT_VERSION) { | |
| var self = this; | |
| setTimeout(function () { self._startNewStepsTour(); }, 1000); | |
| } | |
| }; | |
| GuidedTour.prototype._createTourButton = function () { | |
| var copyLinkBtn = document.getElementById('copyLinkBtn'); | |
| if (!copyLinkBtn) return; | |
| var btn = document.createElement('button'); | |
| btn.id = 'tourBtn'; | |
| btn.className = 'btn btn-outline-light btn-sm me-2'; | |
| btn.title = _t({ en: 'User guide', vi: 'Hướng dẫn sử dụng', ja: 'ユーザーガイド' }); | |
| btn.innerHTML = '<i class="fas fa-question-circle"></i> ' + | |
| (UI_STRINGS[_getLang()] || UI_STRINGS[DEFAULT_LANG]).btnLabel; | |
| var self = this; | |
| btn.addEventListener('click', function () { self.startFullTour(); }); | |
| copyLinkBtn.parentNode.insertBefore(btn, copyLinkBtn); | |
| this._tourBtn = btn; | |
| }; | |
| /** Full tour (all versions) — triggered by button click. */ | |
| GuidedTour.prototype.startFullTour = function () { | |
| this._runTour(_getAllSteps()); | |
| }; | |
| /** Only new (unseen) steps — used for auto-start. */ | |
| GuidedTour.prototype._startNewStepsTour = function () { | |
| var newSteps = _getNewSteps(this._getCompletedVersion()); | |
| if (newSteps.length > 0) this._runTour(newSteps); | |
| }; | |
| /** Backward-compatible alias. */ | |
| GuidedTour.prototype.startTour = function () { this.startFullTour(); }; | |
| GuidedTour.prototype._runTour = function (steps) { | |
| if (!window.driver || !steps || steps.length === 0) return; | |
| var localized = _localizeSteps(steps); | |
| // Filter out steps whose target element doesn't exist | |
| var valid = localized.filter(function (s) { | |
| return !s.element || document.querySelector(s.element); | |
| }); | |
| if (valid.length === 0) return; | |
| var self = this; | |
| var lang = _getLang(); | |
| var ui = UI_STRINGS[lang] || UI_STRINGS[DEFAULT_LANG]; | |
| var driverFn = window.driver.js.driver; | |
| this._driverObj = driverFn({ | |
| showProgress: true, | |
| animate: true, | |
| overlayColor: 'rgba(0, 0, 0, 0.6)', | |
| stagePadding: 8, | |
| popoverClass: 'guided-tour-popover', | |
| nextBtnText: ui.next, | |
| prevBtnText: ui.prev, | |
| doneBtnText: ui.done, | |
| onDestroyStarted: function () { | |
| self.markCompleted(); | |
| if (self._driverObj) self._driverObj.destroy(); | |
| }, | |
| steps: valid | |
| }); | |
| this._driverObj.drive(); | |
| }; | |
| // ─── Version / persistence ──────────────────────────────────────────────── | |
| GuidedTour.prototype.markCompleted = function () { | |
| try { localStorage.setItem(this._storageKey, String(CURRENT_VERSION)); } catch (_) {} | |
| }; | |
| GuidedTour.prototype._getCompletedVersion = function () { | |
| try { | |
| // Migrate old boolean key | |
| var oldKey = 'onnx_explorer_tour_completed'; | |
| var oldVal = localStorage.getItem(oldKey); | |
| if (oldVal === 'true') { | |
| localStorage.removeItem(oldKey); | |
| localStorage.setItem(this._storageKey, '1'); | |
| return 1; | |
| } | |
| var num = parseInt(localStorage.getItem(this._storageKey), 10); | |
| return isNaN(num) ? 0 : num; | |
| } catch (_) { return 0; } | |
| }; | |
| GuidedTour.prototype.isCompleted = function () { | |
| return this._getCompletedVersion() >= CURRENT_VERSION; | |
| }; | |
| GuidedTour.prototype.reset = function () { | |
| try { | |
| localStorage.removeItem(this._storageKey); | |
| localStorage.removeItem('onnx_explorer_tour_completed'); | |
| } catch (_) {} | |
| }; | |
| GuidedTour.prototype.destroy = function () { | |
| if (this._driverObj) { this._driverObj.destroy(); this._driverObj = null; } | |
| if (this._tourBtn && this._tourBtn.parentNode) { | |
| this._tourBtn.parentNode.removeChild(this._tourBtn); | |
| this._tourBtn = null; | |
| } | |
| }; | |
| window.GuidedTour = GuidedTour; | |
| })(); | |