model-explorer / js /ui /guidedTour.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* 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;
})();