omni-viewer-core 0.4.0 → 0.6.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/README.md +29 -1
- package/dist/host/index.d.ts +14 -2
- package/dist/i18n/catalog.en.js +21 -0
- package/dist/i18n/catalog.ja.js +22 -2
- package/dist/i18n/catalog.ko.js +21 -0
- package/dist/i18n/catalog.zh-cn.js +18 -1
- package/dist/parsers/safetensors/index.d.ts +34 -0
- package/dist/parsers/safetensors/index.js +233 -0
- package/dist/parsers/toml/model.d.ts +10 -0
- package/dist/parsers/toml/parse.js +104 -34
- package/dist/parsers/yaml/index.js +14 -2
- package/dist/parsers/yaml/self-loading.js +26 -14
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry/index.js +6 -2
- package/dist/styles/pdf.css +203 -0
- package/dist/styles/safetensors.css +3 -0
- package/dist/styles/toml.css +1 -1
- package/dist/styles/yaml.css +1 -1
- package/dist/viewers/json/controller.js +13 -24
- package/dist/viewers/json/index.js +5 -1
- package/dist/viewers/markdown/index.d.ts +3 -2
- package/dist/viewers/markdown/index.js +29 -14
- package/dist/viewers/pdf/controller.d.ts +22 -2
- package/dist/viewers/pdf/controller.js +32 -10
- package/dist/viewers/pdf/editing.d.ts +11 -0
- package/dist/viewers/pdf/editing.js +19 -16
- package/dist/viewers/pdf/index.d.ts +81 -9
- package/dist/viewers/pdf/index.js +558 -104
- package/dist/viewers/pdf/self-loading.d.ts +3 -3
- package/dist/viewers/pdf/styles.d.ts +1 -1
- package/dist/viewers/pdf/styles.js +203 -0
- package/dist/viewers/safetensors/index.d.ts +19 -0
- package/dist/viewers/safetensors/index.js +170 -0
- package/dist/viewers/safetensors/styles.d.ts +1 -0
- package/dist/viewers/safetensors/styles.js +4 -0
- package/dist/viewers/structured-styles.d.ts +1 -1
- package/dist/viewers/structured-styles.js +1 -1
- package/dist/viewers/structured.d.ts +16 -0
- package/dist/viewers/structured.js +199 -48
- package/dist/viewers/toml/controller.d.ts +27 -1
- package/dist/viewers/toml/controller.js +64 -10
- package/dist/viewers/yaml/controller.js +48 -5
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ models, viewers mount into a DOM element, and everything host-specific
|
|
|
15
15
|
- **Documents** — PDF, Word (DOCX and legacy DOC), HWP, PowerPoint (PPTX and
|
|
16
16
|
legacy PPT), Markdown
|
|
17
17
|
- **Data & spreadsheets** — Excel, CSV/TSV, JSON, JSONL/NDJSON, YAML, TOML,
|
|
18
|
-
Parquet, Avro, HDF5, MATLAB MAT, Protocol Buffers, ReqIF, SQLite
|
|
18
|
+
Parquet, Avro, HDF5, MATLAB MAT, Safetensors, Protocol Buffers, ReqIF, SQLite
|
|
19
19
|
- **Media & graphics** — audio (waveform/spectrogram), video, images,
|
|
20
20
|
Photoshop PSD
|
|
21
21
|
- **Engineering & automotive** — CAN DBC, AUTOSAR ARXML, ASAM A2L, Vector
|
|
@@ -63,6 +63,34 @@ Viewers follow the same pattern — a `mount*Viewer(input, container, ctx,
|
|
|
63
63
|
deps)` entry per format, plus `self-loading` variants that dynamically import
|
|
64
64
|
their own dependencies for hosts that don't want to wire them manually.
|
|
65
65
|
|
|
66
|
+
### PDF host integration
|
|
67
|
+
|
|
68
|
+
The PDF viewer asks `ctx.assets.resolveAssetUrl` for the exact key
|
|
69
|
+
`assets/pdfjs/pdf.worker.min.mjs`. The published package also exposes that
|
|
70
|
+
worker at `omni-viewer-core/assets/pdfjs/pdf.worker.min.mjs`; a host may return
|
|
71
|
+
its own compatible `pdfjs-dist` worker URL instead, or pass `workerSrc` in the
|
|
72
|
+
PDF mount options. `isEvalSupported` defaults to `false` for CSP-safe hosts.
|
|
73
|
+
|
|
74
|
+
PDF mount options are additive and optional. `saveMode` is `hybrid` by default
|
|
75
|
+
(editable text/markup sidecar; signatures and deleted pages are permanent) and
|
|
76
|
+
may be set to `flattened` for smaller output. `toolbarActions` adds host-owned
|
|
77
|
+
buttons without exposing platform APIs to core. `zoomLevels`, `maxMergeBytes`,
|
|
78
|
+
and `onSaveAsComplete` configure navigation and host save behavior.
|
|
79
|
+
|
|
80
|
+
Large save and merge work can be delegated through the optional
|
|
81
|
+
`PdfViewerDeps.processing` service. VS Code extension-host and Web Worker
|
|
82
|
+
adapters can implement `buildPdf` and/or `mergePdfs`; each receives an
|
|
83
|
+
`AbortSignal` and progress callback. When the service is absent, the default
|
|
84
|
+
`auto` mode preserves the browser `pdf-lib` fallback. `host` requires the
|
|
85
|
+
service and `browser` forces the fallback. `PdfViewerHandle.operation` reports
|
|
86
|
+
running, succeeded, failed, and cancelled states, and `cancelOperation()`
|
|
87
|
+
requests cancellation.
|
|
88
|
+
|
|
89
|
+
`FileSaveService.saveFile` may continue resolving `void` for compatibility.
|
|
90
|
+
New adapters should return `{ status: 'cancelled' }` when a Save As picker is
|
|
91
|
+
dismissed, or `{ status: 'saved', fileName?, uri? }` after any host post-save
|
|
92
|
+
work (for example opening the new file or showing a notification) completes.
|
|
93
|
+
|
|
66
94
|
Parsers never throw on malformed input; they return a
|
|
67
95
|
`ParseOutcome` with a typed failure and diagnostics, and they enforce
|
|
68
96
|
resource limits (input size, cell/row/entry counts, declared decompressed
|
package/dist/host/index.d.ts
CHANGED
|
@@ -27,11 +27,23 @@ export type LogLevel = 'info' | 'warn' | 'error';
|
|
|
27
27
|
export interface LoggerService {
|
|
28
28
|
log(level: LogLevel, message: string): void;
|
|
29
29
|
}
|
|
30
|
+
export type FileSaveResult = {
|
|
31
|
+
status: 'saved';
|
|
32
|
+
fileName?: string;
|
|
33
|
+
uri?: string;
|
|
34
|
+
} | {
|
|
35
|
+
status: 'cancelled';
|
|
36
|
+
};
|
|
30
37
|
export interface FileSaveService {
|
|
31
38
|
/** Creates a new file. Implementations present the platform's save
|
|
32
39
|
* destination picker (or browser-equivalent download prompt) so the
|
|
33
|
-
* user chooses the target path and may change the suggested name.
|
|
34
|
-
|
|
40
|
+
* user chooses the target path and may change the suggested name.
|
|
41
|
+
*
|
|
42
|
+
* Existing implementations may continue returning `void` (treated as a
|
|
43
|
+
* successful save). New implementations should return `cancelled` when the
|
|
44
|
+
* picker is dismissed, and may open the saved file or show host UI before
|
|
45
|
+
* resolving `saved`. */
|
|
46
|
+
saveFile(name: string, data: Uint8Array, mimeType: string): Promise<void | FileSaveResult>;
|
|
35
47
|
}
|
|
36
48
|
export interface ClipboardService {
|
|
37
49
|
writeText(text: string): Promise<void>;
|
package/dist/i18n/catalog.en.js
CHANGED
|
@@ -50,6 +50,10 @@ export const CATALOG_EN = {
|
|
|
50
50
|
'hdf5.structure': 'Structure', 'hdf5.noData': 'No data available.',
|
|
51
51
|
'hdf5.matchingRows': '{count} matching rows',
|
|
52
52
|
'hdf5.matchingRowsLimited': '{visible} / {total} matching rows shown',
|
|
53
|
+
'safetensors.title': 'Safetensors', 'safetensors.search': 'Search tensors…', 'safetensors.copyJson': 'Copy JSON',
|
|
54
|
+
'safetensors.structure': 'Structure', 'safetensors.noData': 'No data available.',
|
|
55
|
+
'safetensors.matchingRows': '{count} matching rows',
|
|
56
|
+
'safetensors.matchingRowsLimited': '{visible} / {total} matching rows shown',
|
|
53
57
|
'proto.title': 'Protocol Buffer', 'proto.summary': '{syntax} · {package} · {messages} messages · {services} services',
|
|
54
58
|
'proto.noPackage': 'no package', 'proto.search': 'Search message, field, enum, RPC', 'proto.copyPanel': 'Copy panel',
|
|
55
59
|
'proto.panel.tree': 'Message Tree', 'proto.panel.types': 'Types', 'proto.panel.relationships': 'Relationships',
|
|
@@ -142,12 +146,16 @@ export const CATALOG_EN = {
|
|
|
142
146
|
'diag.yaml.node-limit': 'The YAML node limit was reached.',
|
|
143
147
|
'diag.yaml.depth-limit': 'The YAML nesting depth limit was reached.',
|
|
144
148
|
'diag.yaml.alias-limit': 'The YAML alias limit was reached.',
|
|
149
|
+
'diag.yaml.duplicate-key': 'Duplicate key "{key}"; the last value is used.',
|
|
145
150
|
'structured.tree': 'Tree', 'structured.flat': 'Flat', 'structured.json': 'JSON', 'structured.raw': 'Raw', 'structured.expandAll': 'Expand all',
|
|
146
151
|
'structured.collapseAll': 'Collapse all', 'structured.expand': 'Expand', 'structured.collapse': 'Collapse',
|
|
147
152
|
'structured.search': 'Search', 'structured.copy': 'Copy', 'structured.copyPath': 'Path',
|
|
148
153
|
'structured.copyValue': 'Value', 'structured.copyJson': 'JSON',
|
|
149
154
|
'structured.source': 'Source', 'structured.sourceDescription': 'Editable original text',
|
|
150
155
|
'structured.treeDescription': 'Structured preview',
|
|
156
|
+
'structured.scopeLabel': 'Search in', 'structured.scope.all': 'All', 'structured.scope.key': 'Key',
|
|
157
|
+
'structured.scope.path': 'Path', 'structured.scope.value': 'Value',
|
|
158
|
+
'structured.matchCount': '{count} matches', 'structured.keyCount': '{count} keys', 'structured.itemCount': '{count} items',
|
|
151
159
|
'toml.treeView': 'TOML', 'yaml.treeView': 'YAML', 'jsonl.title': 'JSON Lines',
|
|
152
160
|
'jsonl.loadMore': 'Load more', 'jsonl.loadAll': 'Load all', 'jsonl.save': 'Save',
|
|
153
161
|
'jsonl.editUnavailable': 'Editing requires the complete file.', 'diag.jsonl.load-failed': 'Could not load more JSON Lines data.',
|
|
@@ -327,6 +335,7 @@ export const CATALOG_EN = {
|
|
|
327
335
|
'pdf.goToPage': 'Go to page',
|
|
328
336
|
'pdf.zoomIn': 'Zoom in',
|
|
329
337
|
'pdf.zoomOut': 'Zoom out',
|
|
338
|
+
'pdf.thumbnails': 'Page thumbnails',
|
|
330
339
|
'pdf.viewOptions': 'View options',
|
|
331
340
|
'pdf.fitWidth': 'Fit to width',
|
|
332
341
|
'pdf.fitHeight': 'Fit to height',
|
|
@@ -340,6 +349,13 @@ export const CATALOG_EN = {
|
|
|
340
349
|
'pdf.annotationColor': 'Text color',
|
|
341
350
|
'pdf.save': 'Save',
|
|
342
351
|
'pdf.saveAs': 'Save as',
|
|
352
|
+
'pdf.saving': 'Saving PDF…',
|
|
353
|
+
'pdf.operationCancelled': 'PDF operation cancelled.',
|
|
354
|
+
'pdf.workerLoadFailed': 'Unable to load the PDF worker asset.',
|
|
355
|
+
'pdf.saveModeHybrid': 'Editable',
|
|
356
|
+
'pdf.saveModeHybridTitle': 'Hybrid save: text markup remains editable; signatures and deleted pages cannot be recovered.',
|
|
357
|
+
'pdf.saveModeFlattened': 'Compact',
|
|
358
|
+
'pdf.saveModeFlattenedTitle': 'Flattened save: smaller output without an editable sidecar.',
|
|
343
359
|
'pdf.merge': 'Merge PDF',
|
|
344
360
|
'pdf.merging': 'Merging PDF…',
|
|
345
361
|
'pdf.mergeComplete': 'PDF merged. Save or Save as to keep the changes.',
|
|
@@ -361,6 +377,11 @@ export const CATALOG_EN = {
|
|
|
361
377
|
'pdf.textPlace': 'Click a page to place the text.',
|
|
362
378
|
'pdf.deletePage': 'Delete page',
|
|
363
379
|
'pdf.deleteAnnotation': 'Delete annotation',
|
|
380
|
+
'pdf.markupList': 'Annotations',
|
|
381
|
+
'pdf.closeMarkupList': 'Close annotations',
|
|
382
|
+
'pdf.changeColor': 'Change color',
|
|
383
|
+
'pdf.copyText': 'Copy text',
|
|
384
|
+
'pdf.noMarkups': 'No highlights, underlines, or strikethroughs yet.',
|
|
364
385
|
'pdf.password': 'Password',
|
|
365
386
|
'pdf.passwordRequired': 'This PDF requires a password.',
|
|
366
387
|
'pdf.passwordIncorrect': 'Incorrect password. Try again.',
|
package/dist/i18n/catalog.ja.js
CHANGED
|
@@ -46,6 +46,10 @@ export const CATALOG_JA = {
|
|
|
46
46
|
'hdf5.structure': '構造', 'hdf5.noData': '表示できるデータがありません。',
|
|
47
47
|
'hdf5.matchingRows': '一致する行: {count}件',
|
|
48
48
|
'hdf5.matchingRowsLimited': '一致する{total}行中{visible}行を表示',
|
|
49
|
+
'safetensors.title': 'Safetensors', 'safetensors.search': 'テンソルを検索…', 'safetensors.copyJson': 'JSONをコピー',
|
|
50
|
+
'safetensors.structure': '構造', 'safetensors.noData': '表示できるデータがありません。',
|
|
51
|
+
'safetensors.matchingRows': '一致する行: {count}件',
|
|
52
|
+
'safetensors.matchingRowsLimited': '一致する{total}行中{visible}行を表示',
|
|
49
53
|
'proto.title': 'Protocol Buffer', 'proto.summary': '{syntax} · {package} · メッセージ{messages}件 · サービス{services}件',
|
|
50
54
|
'proto.noPackage': 'パッケージなし', 'proto.search': 'メッセージ、フィールド、列挙型、RPCを検索', 'proto.copyPanel': 'パネルをコピー',
|
|
51
55
|
'proto.panel.tree': 'メッセージツリー', 'proto.panel.types': '型', 'proto.panel.relationships': '関係',
|
|
@@ -146,12 +150,16 @@ export const CATALOG_JA = {
|
|
|
146
150
|
'diag.yaml.node-limit': 'YAMLノードの上限に達しました。',
|
|
147
151
|
'diag.yaml.depth-limit': 'YAMLのネスト深度上限に達しました。',
|
|
148
152
|
'diag.yaml.alias-limit': 'YAMLエイリアスの上限に達しました。',
|
|
153
|
+
'diag.yaml.duplicate-key': 'キー「{key}」が重複しています。最後の値が使用されます。',
|
|
149
154
|
'structured.tree': 'ツリー', 'structured.flat': 'フラット', 'structured.json': 'JSON', 'structured.raw': 'RAW', 'structured.expandAll': 'すべて展開',
|
|
150
155
|
'structured.collapseAll': 'すべて折りたたむ', 'structured.expand': '展開', 'structured.collapse': '折りたたむ',
|
|
151
156
|
'structured.search': '検索', 'structured.copy': 'コピー', 'structured.copyPath': 'パス',
|
|
152
157
|
'structured.copyValue': '値', 'structured.copyJson': 'JSON',
|
|
153
158
|
'structured.source': 'ソース', 'structured.sourceDescription': '編集可能な元のテキスト',
|
|
154
159
|
'structured.treeDescription': '構造化プレビュー',
|
|
160
|
+
'structured.scopeLabel': '検索対象', 'structured.scope.all': 'すべて', 'structured.scope.key': 'キー',
|
|
161
|
+
'structured.scope.path': 'パス', 'structured.scope.value': '値',
|
|
162
|
+
'structured.matchCount': '{count} 件一致', 'structured.keyCount': '{count} 個のキー', 'structured.itemCount': '{count} 個の項目',
|
|
155
163
|
'toml.treeView': 'TOML', 'yaml.treeView': 'YAML', 'jsonl.title': 'JSON Lines',
|
|
156
164
|
'jsonl.loadMore': 'さらに読み込む', 'jsonl.loadAll': 'すべて読み込む', 'jsonl.save': '保存',
|
|
157
165
|
'jsonl.editUnavailable': '編集には完全なファイルが必要です。', 'diag.jsonl.load-failed': 'JSON Linesデータを追加で読み込めませんでした。',
|
|
@@ -242,12 +250,20 @@ export const CATALOG_JA = {
|
|
|
242
250
|
'json.result.copy': 'コピー', 'json.result.replace': 'エディターを置換', 'json.result.close': '閉じる',
|
|
243
251
|
'pdf.title': 'PDF', 'pdf.loading': 'PDFを読み込み中…', 'pdf.loadFailed': 'このPDFを読み込めません。',
|
|
244
252
|
'pdf.pages': 'ページ', 'pdf.goToPage': 'ページへ移動', 'pdf.zoomIn': '拡大', 'pdf.zoomOut': '縮小',
|
|
253
|
+
'pdf.thumbnails': 'ページサムネイル',
|
|
245
254
|
'pdf.viewOptions': '表示オプション', 'pdf.fitWidth': '幅に合わせる', 'pdf.fitHeight': '高さに合わせる',
|
|
246
255
|
'pdf.singlePage': '単一ページ', 'pdf.twoPagesOdd': '見開き(奇数ページを左)',
|
|
247
256
|
'pdf.twoPagesEven': '見開き(偶数ページを左)', 'pdf.matchTheme': 'テーマの色に合わせる',
|
|
248
257
|
'pdf.resetPages': 'ページをリセット', 'pdf.annotationText': 'テキストを追加',
|
|
249
258
|
'pdf.annotationSize': 'テキストサイズ', 'pdf.annotationColor': 'テキストの色',
|
|
250
|
-
'pdf.save': '保存', 'pdf.saveAs': '名前を付けて保存', 'pdf.
|
|
259
|
+
'pdf.save': '保存', 'pdf.saveAs': '名前を付けて保存', 'pdf.saving': 'PDFを保存中…',
|
|
260
|
+
'pdf.operationCancelled': 'PDFの処理をキャンセルしました。',
|
|
261
|
+
'pdf.workerLoadFailed': 'PDF Workerアセットを読み込めません。',
|
|
262
|
+
'pdf.saveModeHybrid': '編集可能',
|
|
263
|
+
'pdf.saveModeHybridTitle': 'ハイブリッド保存:テキストマークアップは再編集できます。署名と削除したページは復元できません。',
|
|
264
|
+
'pdf.saveModeFlattened': 'コンパクト',
|
|
265
|
+
'pdf.saveModeFlattenedTitle': 'フラット化保存:編集用サイドカーを含まない小さいファイルです。',
|
|
266
|
+
'pdf.merge': 'PDFを結合',
|
|
251
267
|
'pdf.merging': 'PDFを結合中…', 'pdf.mergeComplete': 'PDFを結合しました。変更を保持するには保存してください。',
|
|
252
268
|
'pdf.mergeFailed': 'このPDFを結合できません。', 'pdf.signature': '署名', 'pdf.signatureColor': '署名の色',
|
|
253
269
|
'pdf.signatureClear': 'クリア', 'pdf.signatureUse': '署名を使用',
|
|
@@ -257,7 +273,11 @@ export const CATALOG_JA = {
|
|
|
257
273
|
'pdf.markupOptions': 'テキストマークアップのオプション', 'pdf.underline': '下線',
|
|
258
274
|
'pdf.underlineColor': '下線の色', 'pdf.strikeout': '取り消し線', 'pdf.strikeoutColor': '取り消し線の色',
|
|
259
275
|
'pdf.cancel': 'キャンセル', 'pdf.textPlace': 'ページをクリックしてテキストを配置してください。',
|
|
260
|
-
'pdf.deletePage': 'ページを削除', 'pdf.deleteAnnotation': '注釈を削除',
|
|
276
|
+
'pdf.deletePage': 'ページを削除', 'pdf.deleteAnnotation': '注釈を削除',
|
|
277
|
+
'pdf.markupList': '注釈一覧', 'pdf.closeMarkupList': '注釈一覧を閉じる',
|
|
278
|
+
'pdf.changeColor': '色を変更', 'pdf.copyText': 'テキストをコピー',
|
|
279
|
+
'pdf.noMarkups': 'ハイライト・下線・取り消し線はまだありません。',
|
|
280
|
+
'pdf.password': 'パスワード',
|
|
261
281
|
'pdf.passwordRequired': 'このPDFにはパスワードが必要です。',
|
|
262
282
|
'pdf.passwordIncorrect': 'パスワードが正しくありません。もう一度お試しください。',
|
|
263
283
|
'pdf.passwordSubmit': '開く', 'pdf.editingUnavailable': 'この環境ではPDFを編集できません',
|
package/dist/i18n/catalog.ko.js
CHANGED
|
@@ -48,6 +48,10 @@ export const CATALOG_KO = {
|
|
|
48
48
|
'hdf5.structure': '구조', 'hdf5.noData': '표시할 데이터가 없습니다.',
|
|
49
49
|
'hdf5.matchingRows': '일치하는 행 {count}개',
|
|
50
50
|
'hdf5.matchingRowsLimited': '전체 {total}행 중 일치하는 {visible}행 표시',
|
|
51
|
+
'safetensors.title': 'Safetensors', 'safetensors.search': '텐서 검색…', 'safetensors.copyJson': 'JSON 복사',
|
|
52
|
+
'safetensors.structure': '구조', 'safetensors.noData': '표시할 데이터가 없습니다.',
|
|
53
|
+
'safetensors.matchingRows': '일치하는 행 {count}개',
|
|
54
|
+
'safetensors.matchingRowsLimited': '전체 {total}행 중 일치하는 {visible}행 표시',
|
|
51
55
|
'proto.title': 'Protocol Buffer', 'proto.summary': '{syntax} · {package} · 메시지 {messages}개 · 서비스 {services}개',
|
|
52
56
|
'proto.noPackage': '패키지 없음', 'proto.search': '메시지, 필드, enum, RPC 검색', 'proto.copyPanel': '패널 복사',
|
|
53
57
|
'proto.panel.tree': '메시지 트리', 'proto.panel.types': '타입', 'proto.panel.relationships': '관계',
|
|
@@ -142,12 +146,16 @@ export const CATALOG_KO = {
|
|
|
142
146
|
'diag.yaml.node-limit': 'YAML 노드 한도에 도달했습니다.',
|
|
143
147
|
'diag.yaml.depth-limit': 'YAML 중첩 깊이 한도에 도달했습니다.',
|
|
144
148
|
'diag.yaml.alias-limit': 'YAML 별칭 한도에 도달했습니다.',
|
|
149
|
+
'diag.yaml.duplicate-key': '중복된 키 "{key}"; 마지막 값이 사용됩니다.',
|
|
145
150
|
'structured.tree': '트리', 'structured.flat': '평면', 'structured.json': 'JSON', 'structured.raw': '원시', 'structured.expandAll': '모두 펼치기',
|
|
146
151
|
'structured.collapseAll': '모두 접기', 'structured.expand': '펼치기', 'structured.collapse': '접기',
|
|
147
152
|
'structured.search': '검색', 'structured.copy': '복사', 'structured.copyPath': '경로',
|
|
148
153
|
'structured.copyValue': '값', 'structured.copyJson': 'JSON',
|
|
149
154
|
'structured.source': '소스', 'structured.sourceDescription': '편집 가능한 원본 텍스트',
|
|
150
155
|
'structured.treeDescription': '구조화된 미리보기',
|
|
156
|
+
'structured.scopeLabel': '검색 범위', 'structured.scope.all': '전체', 'structured.scope.key': '키',
|
|
157
|
+
'structured.scope.path': '경로', 'structured.scope.value': '값',
|
|
158
|
+
'structured.matchCount': '{count}개 일치', 'structured.keyCount': '키 {count}개', 'structured.itemCount': '항목 {count}개',
|
|
151
159
|
'toml.treeView': 'TOML', 'yaml.treeView': 'YAML', 'jsonl.title': 'JSON Lines',
|
|
152
160
|
'jsonl.loadMore': '더 불러오기', 'jsonl.loadAll': '모두 불러오기', 'jsonl.save': '저장',
|
|
153
161
|
'jsonl.editUnavailable': '편집하려면 전체 파일이 필요합니다.', 'diag.jsonl.load-failed': 'JSON Lines 데이터를 더 불러올 수 없습니다.',
|
|
@@ -327,6 +335,7 @@ export const CATALOG_KO = {
|
|
|
327
335
|
'pdf.goToPage': '페이지 이동',
|
|
328
336
|
'pdf.zoomIn': '확대',
|
|
329
337
|
'pdf.zoomOut': '축소',
|
|
338
|
+
'pdf.thumbnails': '페이지 썸네일',
|
|
330
339
|
'pdf.viewOptions': '보기 옵션',
|
|
331
340
|
'pdf.fitWidth': '너비 맞춤',
|
|
332
341
|
'pdf.fitHeight': '높이 맞춤',
|
|
@@ -340,6 +349,13 @@ export const CATALOG_KO = {
|
|
|
340
349
|
'pdf.annotationColor': '텍스트 색상',
|
|
341
350
|
'pdf.save': '저장',
|
|
342
351
|
'pdf.saveAs': '다른 이름으로 저장',
|
|
352
|
+
'pdf.saving': 'PDF 저장 중…',
|
|
353
|
+
'pdf.operationCancelled': 'PDF 작업이 취소되었습니다.',
|
|
354
|
+
'pdf.workerLoadFailed': 'PDF Worker 자산을 불러올 수 없습니다.',
|
|
355
|
+
'pdf.saveModeHybrid': '재편집 가능',
|
|
356
|
+
'pdf.saveModeHybridTitle': '하이브리드 저장: 텍스트 마크업은 재편집할 수 있으며 서명과 삭제한 페이지는 복구할 수 없습니다.',
|
|
357
|
+
'pdf.saveModeFlattened': '용량 우선',
|
|
358
|
+
'pdf.saveModeFlattenedTitle': '평면화 저장: 편집용 sidecar 없이 더 작은 파일로 저장합니다.',
|
|
343
359
|
'pdf.merge': 'PDF 병합',
|
|
344
360
|
'pdf.merging': 'PDF 병합 중…',
|
|
345
361
|
'pdf.mergeComplete': 'PDF가 병합되었습니다. 저장 또는 다른 이름으로 저장하여 변경 사항을 유지하세요.',
|
|
@@ -361,6 +377,11 @@ export const CATALOG_KO = {
|
|
|
361
377
|
'pdf.textPlace': '페이지를 클릭하여 텍스트를 배치하세요.',
|
|
362
378
|
'pdf.deletePage': '페이지 삭제',
|
|
363
379
|
'pdf.deleteAnnotation': '주석 삭제',
|
|
380
|
+
'pdf.markupList': '주석 목록',
|
|
381
|
+
'pdf.closeMarkupList': '주석 목록 닫기',
|
|
382
|
+
'pdf.changeColor': '색상 변경',
|
|
383
|
+
'pdf.copyText': '텍스트 복사',
|
|
384
|
+
'pdf.noMarkups': '아직 하이라이트, 밑줄, 취소선이 없습니다.',
|
|
364
385
|
'pdf.password': '암호',
|
|
365
386
|
'pdf.passwordRequired': '이 PDF에는 암호가 필요합니다.',
|
|
366
387
|
'pdf.passwordIncorrect': '암호가 올바르지 않습니다. 다시 시도하세요.',
|
|
@@ -40,6 +40,9 @@ export const CATALOG_ZH_CN = {
|
|
|
40
40
|
'hdf5.title': 'HDF5', 'hdf5.search': '搜索表格…', 'hdf5.copyJson': '复制 JSON',
|
|
41
41
|
'hdf5.structure': '结构', 'hdf5.noData': '没有可用数据。',
|
|
42
42
|
'hdf5.matchingRows': '{count} 个匹配行', 'hdf5.matchingRowsLimited': '显示 {visible} / {total} 个匹配行',
|
|
43
|
+
'safetensors.title': 'Safetensors', 'safetensors.search': '搜索张量…', 'safetensors.copyJson': '复制 JSON',
|
|
44
|
+
'safetensors.structure': '结构', 'safetensors.noData': '没有可用数据。',
|
|
45
|
+
'safetensors.matchingRows': '{count} 个匹配行', 'safetensors.matchingRowsLimited': '显示 {visible} / {total} 个匹配行',
|
|
43
46
|
'proto.title': 'Protocol Buffer', 'proto.summary': '{syntax} · {package} · {messages} 条消息 · {services} 个服务',
|
|
44
47
|
'proto.noPackage': '无软件包', 'proto.search': '搜索消息、字段、枚举或 RPC', 'proto.copyPanel': '复制面板',
|
|
45
48
|
'proto.panel.tree': '消息树', 'proto.panel.types': '类型', 'proto.panel.relationships': '关系',
|
|
@@ -134,12 +137,16 @@ export const CATALOG_ZH_CN = {
|
|
|
134
137
|
'diag.yaml.document-limit': '仅解析了前 {count} 个 YAML 文档。',
|
|
135
138
|
'diag.yaml.node-limit': '已达到 YAML 节点限制。', 'diag.yaml.depth-limit': '已达到 YAML 嵌套深度限制。',
|
|
136
139
|
'diag.yaml.alias-limit': '已达到 YAML 别名限制。',
|
|
140
|
+
'diag.yaml.duplicate-key': '键 "{key}" 重复;将使用最后一个值。',
|
|
137
141
|
'structured.tree': '树', 'structured.flat': '平铺', 'structured.json': 'JSON', 'structured.raw': '原始',
|
|
138
142
|
'structured.expandAll': '全部展开', 'structured.collapseAll': '全部折叠',
|
|
139
143
|
'structured.expand': '展开', 'structured.collapse': '折叠', 'structured.search': '搜索',
|
|
140
144
|
'structured.copy': '复制', 'structured.copyPath': '路径', 'structured.copyValue': '值', 'structured.copyJson': 'JSON',
|
|
141
145
|
'structured.source': '源代码', 'structured.sourceDescription': '可编辑的原始文本',
|
|
142
146
|
'structured.treeDescription': '结构化预览',
|
|
147
|
+
'structured.scopeLabel': '搜索范围', 'structured.scope.all': '全部', 'structured.scope.key': '键',
|
|
148
|
+
'structured.scope.path': '路径', 'structured.scope.value': '值',
|
|
149
|
+
'structured.matchCount': '{count} 项匹配', 'structured.keyCount': '{count} 个键', 'structured.itemCount': '{count} 个项目',
|
|
143
150
|
'toml.treeView': 'TOML', 'yaml.treeView': 'YAML', 'jsonl.title': 'JSON Lines',
|
|
144
151
|
'jsonl.loadMore': '加载更多', 'jsonl.loadAll': '全部加载', 'jsonl.save': '保存',
|
|
145
152
|
'jsonl.editUnavailable': '编辑需要完整文件。', 'diag.jsonl.load-failed': '无法加载更多 JSON Lines 数据。',
|
|
@@ -226,11 +233,18 @@ export const CATALOG_ZH_CN = {
|
|
|
226
233
|
'json.result.copy': '复制', 'json.result.replace': '替换编辑器内容', 'json.result.close': '关闭',
|
|
227
234
|
'pdf.title': 'PDF', 'pdf.loading': '正在加载 PDF…', 'pdf.loadFailed': '无法加载此 PDF。',
|
|
228
235
|
'pdf.pages': '页', 'pdf.goToPage': '转到页面', 'pdf.zoomIn': '放大', 'pdf.zoomOut': '缩小',
|
|
236
|
+
'pdf.thumbnails': '页面缩略图',
|
|
229
237
|
'pdf.viewOptions': '视图选项', 'pdf.fitWidth': '适合宽度', 'pdf.fitHeight': '适合高度',
|
|
230
238
|
'pdf.singlePage': '单页', 'pdf.twoPagesOdd': '双页(奇数页在左)', 'pdf.twoPagesEven': '双页(偶数页在左)',
|
|
231
239
|
'pdf.matchTheme': '匹配主题颜色', 'pdf.resetPages': '重置页面',
|
|
232
240
|
'pdf.annotationText': '添加文本', 'pdf.annotationSize': '文本大小', 'pdf.annotationColor': '文本颜色',
|
|
233
|
-
'pdf.save': '保存', 'pdf.saveAs': '另存为', 'pdf.
|
|
241
|
+
'pdf.save': '保存', 'pdf.saveAs': '另存为', 'pdf.saving': '正在保存 PDF…',
|
|
242
|
+
'pdf.operationCancelled': 'PDF 操作已取消。', 'pdf.workerLoadFailed': '无法加载 PDF Worker 资源。',
|
|
243
|
+
'pdf.saveModeHybrid': '可重新编辑',
|
|
244
|
+
'pdf.saveModeHybridTitle': '混合保存:文本标记可重新编辑;签名和已删除页面无法恢复。',
|
|
245
|
+
'pdf.saveModeFlattened': '精简',
|
|
246
|
+
'pdf.saveModeFlattenedTitle': '扁平化保存:不含编辑 sidecar,文件更小。',
|
|
247
|
+
'pdf.merge': '合并 PDF', 'pdf.merging': '正在合并 PDF…',
|
|
234
248
|
'pdf.mergeComplete': 'PDF 已合并。请保存或另存为以保留更改。', 'pdf.mergeFailed': '无法合并此 PDF。',
|
|
235
249
|
'pdf.signature': '签名', 'pdf.signatureColor': '签名颜色', 'pdf.signatureClear': '清除',
|
|
236
250
|
'pdf.signatureUse': '使用签名', 'pdf.signaturePlace': '点击页面以放置签名。',
|
|
@@ -238,6 +252,9 @@ export const CATALOG_ZH_CN = {
|
|
|
238
252
|
'pdf.markupOptions': '文本标记选项', 'pdf.underline': '下划线', 'pdf.underlineColor': '下划线颜色',
|
|
239
253
|
'pdf.strikeout': '删除线', 'pdf.strikeoutColor': '删除线颜色', 'pdf.cancel': '取消',
|
|
240
254
|
'pdf.textPlace': '点击页面以放置文本。', 'pdf.deletePage': '删除页面', 'pdf.deleteAnnotation': '删除批注',
|
|
255
|
+
'pdf.markupList': '批注列表', 'pdf.closeMarkupList': '关闭批注列表',
|
|
256
|
+
'pdf.changeColor': '更改颜色', 'pdf.copyText': '复制文本',
|
|
257
|
+
'pdf.noMarkups': '尚无高亮、下划线或删除线。',
|
|
241
258
|
'pdf.password': '密码', 'pdf.passwordRequired': '此 PDF 需要密码。',
|
|
242
259
|
'pdf.passwordIncorrect': '密码错误,请重试。', 'pdf.passwordSubmit': '打开',
|
|
243
260
|
'pdf.editingUnavailable': '此平台无法编辑 PDF', 'pdf.noFilePick': '此平台无法选择要合并的文件',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface SafetensorsSummaryItem {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string | number;
|
|
4
|
+
}
|
|
5
|
+
export interface SafetensorsTable {
|
|
6
|
+
title: string;
|
|
7
|
+
headers: string[];
|
|
8
|
+
rows: Array<Array<string | number>>;
|
|
9
|
+
}
|
|
10
|
+
export interface SafetensorsDocument {
|
|
11
|
+
format: 'safetensors';
|
|
12
|
+
title: string;
|
|
13
|
+
fileSize: string;
|
|
14
|
+
summary: SafetensorsSummaryItem[];
|
|
15
|
+
tables: SafetensorsTable[];
|
|
16
|
+
rawPreview?: string | undefined;
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
/** One tensor as declared in the safetensors header. */
|
|
20
|
+
export interface SafetensorsTensor {
|
|
21
|
+
name: string;
|
|
22
|
+
dtype: string;
|
|
23
|
+
shape: number[];
|
|
24
|
+
/** [begin, end) byte range inside the tensor data buffer. */
|
|
25
|
+
dataOffsets: [number, number];
|
|
26
|
+
/** Product of `shape` (1 for a scalar / empty shape). */
|
|
27
|
+
elements: number;
|
|
28
|
+
/** `end - begin`, the bytes the tensor occupies. */
|
|
29
|
+
byteLength: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function parseSafetensors(input: Uint8Array, fileSize?: string): SafetensorsDocument;
|
|
32
|
+
/** Compact parameter counts, e.g. 1.20B, 350.0M, 12.3K. */
|
|
33
|
+
export declare function formatCount(count: number): string;
|
|
34
|
+
export declare function formatFileSize(bytes: number): string;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal safetensors (.safetensors) header reader.
|
|
3
|
+
*
|
|
4
|
+
* The format is a fixed 8-byte little-endian header length, followed by a UTF-8
|
|
5
|
+
* JSON header (tensor name → {dtype, shape, data_offsets}, plus an optional
|
|
6
|
+
* "__metadata__" string map), followed by the raw tensor buffer. Only the header
|
|
7
|
+
* is decoded here — the tensor payloads are never read, so inspecting a
|
|
8
|
+
* multi-gigabyte checkpoint stays cheap.
|
|
9
|
+
*
|
|
10
|
+
* Reference: https://github.com/huggingface/safetensors
|
|
11
|
+
*/
|
|
12
|
+
const HEADER_LENGTH_BYTES = 8;
|
|
13
|
+
// A safetensors header is JSON metadata only; real files stay well under this.
|
|
14
|
+
// The cap guards against a corrupt length field claiming gigabytes of "header".
|
|
15
|
+
const MAX_HEADER_BYTES = 100_000_000;
|
|
16
|
+
const METADATA_KEY = '__metadata__';
|
|
17
|
+
/** Bits per element for the dtypes supported by safetensors 0.8. */
|
|
18
|
+
const DTYPE_BITS = {
|
|
19
|
+
F64: 64, C64: 64, I64: 64, U64: 64,
|
|
20
|
+
F32: 32, I32: 32, U32: 32,
|
|
21
|
+
F16: 16, BF16: 16, I16: 16, U16: 16,
|
|
22
|
+
I8: 8, U8: 8, BOOL: 8,
|
|
23
|
+
F8_E4M3: 8, F8_E5M2: 8, F8_E8M0: 8,
|
|
24
|
+
F8_E4M3FNUZ: 8, F8_E5M2FNUZ: 8,
|
|
25
|
+
F6_E2M3: 6, F6_E3M2: 6, F4: 4
|
|
26
|
+
};
|
|
27
|
+
export function parseSafetensors(input, fileSize = formatFileSize(input.byteLength)) {
|
|
28
|
+
const warnings = [];
|
|
29
|
+
if (input.byteLength < HEADER_LENGTH_BYTES) {
|
|
30
|
+
return invalid(fileSize, 'File is too small to contain a safetensors header.');
|
|
31
|
+
}
|
|
32
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
33
|
+
const headerLength = Number(view.getBigUint64(0, true));
|
|
34
|
+
if (!Number.isSafeInteger(headerLength) || headerLength <= 0 || headerLength > MAX_HEADER_BYTES) {
|
|
35
|
+
return invalid(fileSize, 'Header length is out of range; the file is not a valid safetensors file.');
|
|
36
|
+
}
|
|
37
|
+
const headerEnd = HEADER_LENGTH_BYTES + headerLength;
|
|
38
|
+
if (headerEnd > input.byteLength) {
|
|
39
|
+
return invalid(fileSize, 'Declared header length extends past the end of the file.');
|
|
40
|
+
}
|
|
41
|
+
const headerBytes = input.subarray(HEADER_LENGTH_BYTES, headerEnd);
|
|
42
|
+
if (headerBytes[0] !== 0x7b) {
|
|
43
|
+
return invalid(fileSize, 'The safetensors header must begin with a JSON object.');
|
|
44
|
+
}
|
|
45
|
+
let header;
|
|
46
|
+
try {
|
|
47
|
+
const json = new TextDecoder('utf-8', { fatal: true }).decode(headerBytes);
|
|
48
|
+
const parsed = JSON.parse(json);
|
|
49
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
50
|
+
return invalid(fileSize, 'The safetensors header is not a JSON object.');
|
|
51
|
+
}
|
|
52
|
+
header = parsed;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return invalid(fileSize, 'The safetensors header is not valid JSON.');
|
|
56
|
+
}
|
|
57
|
+
const metadata = {};
|
|
58
|
+
const rawMeta = header[METADATA_KEY];
|
|
59
|
+
let metadataIssue = false;
|
|
60
|
+
if (rawMeta !== undefined) {
|
|
61
|
+
if (rawMeta !== null && typeof rawMeta === 'object' && !Array.isArray(rawMeta)) {
|
|
62
|
+
for (const [key, value] of Object.entries(rawMeta)) {
|
|
63
|
+
if (typeof value === 'string')
|
|
64
|
+
metadata[key] = value;
|
|
65
|
+
else
|
|
66
|
+
metadataIssue = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
metadataIssue = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (metadataIssue)
|
|
74
|
+
warnings.push('Some "__metadata__" values are not strings and were ignored.');
|
|
75
|
+
const dataBufferSize = input.byteLength - headerEnd;
|
|
76
|
+
const tensors = [];
|
|
77
|
+
const dtypeCounts = new Map();
|
|
78
|
+
let totalElements = 0;
|
|
79
|
+
let unknownDtype = false;
|
|
80
|
+
let entryIssue = false;
|
|
81
|
+
let rangeIssue = false;
|
|
82
|
+
const validRanges = [];
|
|
83
|
+
for (const [name, rawValue] of Object.entries(header)) {
|
|
84
|
+
if (name === METADATA_KEY)
|
|
85
|
+
continue;
|
|
86
|
+
const entry = rawValue;
|
|
87
|
+
const dtypeValid = typeof entry?.dtype === 'string';
|
|
88
|
+
const dtype = dtypeValid ? entry.dtype : 'unknown';
|
|
89
|
+
const shapeValid = Array.isArray(entry?.shape)
|
|
90
|
+
&& entry.shape.every(n => Number.isSafeInteger(n) && n >= 0);
|
|
91
|
+
const shape = shapeValid ? entry.shape : [];
|
|
92
|
+
const offsets = Array.isArray(entry?.data_offsets) ? entry.data_offsets : [];
|
|
93
|
+
const offsetsValid = offsets.length === 2
|
|
94
|
+
&& offsets.every(n => Number.isSafeInteger(n) && n >= 0);
|
|
95
|
+
const begin = offsetsValid ? offsets[0] : 0;
|
|
96
|
+
const end = offsetsValid ? offsets[1] : 0;
|
|
97
|
+
if (!dtypeValid || !shapeValid || !offsetsValid)
|
|
98
|
+
entryIssue = true;
|
|
99
|
+
const elements = safeElementCount(shape);
|
|
100
|
+
if (elements === undefined)
|
|
101
|
+
entryIssue = true;
|
|
102
|
+
const elementCount = elements ?? 0;
|
|
103
|
+
const byteLength = Math.max(0, end - begin);
|
|
104
|
+
tensors.push({ name, dtype, shape, dataOffsets: [begin, end], elements: elementCount, byteLength });
|
|
105
|
+
totalElements += elementCount;
|
|
106
|
+
dtypeCounts.set(dtype, (dtypeCounts.get(dtype) ?? 0) + 1);
|
|
107
|
+
const bits = DTYPE_BITS[dtype];
|
|
108
|
+
if (bits === undefined) {
|
|
109
|
+
unknownDtype = true;
|
|
110
|
+
}
|
|
111
|
+
else if (elements !== undefined) {
|
|
112
|
+
const expectedBits = BigInt(elements) * BigInt(bits);
|
|
113
|
+
if (expectedBits % 8n !== 0n || expectedBits / 8n !== BigInt(byteLength))
|
|
114
|
+
rangeIssue = true;
|
|
115
|
+
}
|
|
116
|
+
if (!offsetsValid || end < begin || end > dataBufferSize) {
|
|
117
|
+
rangeIssue = true;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
validRanges.push([begin, end]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Header keys have no defined order; sort tensors by their data offset so the
|
|
124
|
+
// table reads in storage order.
|
|
125
|
+
tensors.sort((a, b) => a.dataOffsets[0] - b.dataOffsets[0] || a.dataOffsets[1] - b.dataOffsets[1]);
|
|
126
|
+
// A valid safetensors data buffer is packed from byte zero to EOF without
|
|
127
|
+
// holes or overlaps. Empty tensors may repeat the current offset.
|
|
128
|
+
validRanges.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
129
|
+
let coveredUntil = 0;
|
|
130
|
+
for (const [begin, end] of validRanges) {
|
|
131
|
+
if (begin !== coveredUntil)
|
|
132
|
+
rangeIssue = true;
|
|
133
|
+
coveredUntil = Math.max(coveredUntil, end);
|
|
134
|
+
}
|
|
135
|
+
if (coveredUntil !== dataBufferSize)
|
|
136
|
+
rangeIssue = true;
|
|
137
|
+
if (entryIssue)
|
|
138
|
+
warnings.push('Some tensor entries have an invalid dtype, shape, or data offset declaration.');
|
|
139
|
+
if (unknownDtype)
|
|
140
|
+
warnings.push('Some tensors use a dtype this viewer does not recognize.');
|
|
141
|
+
if (rangeIssue)
|
|
142
|
+
warnings.push('Some tensor byte ranges are inconsistent with their dtype and shape.');
|
|
143
|
+
const dtypeList = [...dtypeCounts.keys()].sort();
|
|
144
|
+
const summary = [
|
|
145
|
+
{ label: 'Tensors', value: tensors.length },
|
|
146
|
+
{ label: 'Parameters', value: formatCount(totalElements) },
|
|
147
|
+
{ label: 'Data types', value: dtypeList.length ? dtypeList.join(', ') : '—' }
|
|
148
|
+
];
|
|
149
|
+
if (Object.keys(metadata).length) {
|
|
150
|
+
summary.push({ label: 'Metadata keys', value: Object.keys(metadata).length });
|
|
151
|
+
}
|
|
152
|
+
const tables = [
|
|
153
|
+
{
|
|
154
|
+
title: `Tensors (${tensors.length})`,
|
|
155
|
+
headers: ['Name', 'Dtype', 'Shape', 'Parameters', 'Size'],
|
|
156
|
+
rows: tensors.map(tensor => [
|
|
157
|
+
tensor.name,
|
|
158
|
+
tensor.dtype,
|
|
159
|
+
tensor.shape.length ? tensor.shape.join(' × ') : 'scalar',
|
|
160
|
+
tensor.elements,
|
|
161
|
+
formatFileSize(tensor.byteLength)
|
|
162
|
+
])
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
if (Object.keys(metadata).length) {
|
|
166
|
+
tables.push({
|
|
167
|
+
title: `Metadata (${Object.keys(metadata).length})`,
|
|
168
|
+
headers: ['Key', 'Value'],
|
|
169
|
+
rows: Object.entries(metadata).map(([key, value]) => [key, value])
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const rawPreview = tensors.length
|
|
173
|
+
? tensors
|
|
174
|
+
.map(tensor => `${tensor.name} [${tensor.shape.length ? tensor.shape.join(' × ') : 'scalar'}] ${tensor.dtype}`)
|
|
175
|
+
.join('\n')
|
|
176
|
+
: undefined;
|
|
177
|
+
return {
|
|
178
|
+
format: 'safetensors',
|
|
179
|
+
title: 'Safetensors tensor collection',
|
|
180
|
+
fileSize,
|
|
181
|
+
summary,
|
|
182
|
+
tables,
|
|
183
|
+
rawPreview,
|
|
184
|
+
warnings
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function safeElementCount(shape) {
|
|
188
|
+
let count = 1;
|
|
189
|
+
for (const dimension of shape) {
|
|
190
|
+
if (!Number.isSafeInteger(dimension) || dimension < 0)
|
|
191
|
+
return undefined;
|
|
192
|
+
if (dimension !== 0 && count > Number.MAX_SAFE_INTEGER / dimension)
|
|
193
|
+
return undefined;
|
|
194
|
+
count *= dimension;
|
|
195
|
+
}
|
|
196
|
+
return count;
|
|
197
|
+
}
|
|
198
|
+
function invalid(fileSize, message) {
|
|
199
|
+
return {
|
|
200
|
+
format: 'safetensors',
|
|
201
|
+
title: 'Safetensors tensor collection',
|
|
202
|
+
fileSize,
|
|
203
|
+
summary: [{ label: 'Status', value: 'invalid' }],
|
|
204
|
+
tables: [],
|
|
205
|
+
rawPreview: undefined,
|
|
206
|
+
warnings: [message]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** Compact parameter counts, e.g. 1.20B, 350.0M, 12.3K. */
|
|
210
|
+
export function formatCount(count) {
|
|
211
|
+
if (count < 1000)
|
|
212
|
+
return String(count);
|
|
213
|
+
const units = [[1e9, 'B'], [1e6, 'M'], [1e3, 'K']];
|
|
214
|
+
for (const [threshold, suffix] of units) {
|
|
215
|
+
if (count >= threshold) {
|
|
216
|
+
const value = count / threshold;
|
|
217
|
+
return value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2) + suffix;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return String(count);
|
|
221
|
+
}
|
|
222
|
+
export function formatFileSize(bytes) {
|
|
223
|
+
if (bytes < 1024)
|
|
224
|
+
return String(bytes) + ' bytes';
|
|
225
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
226
|
+
let value = bytes / 1024;
|
|
227
|
+
let unit = units[0];
|
|
228
|
+
for (let i = 1; i < units.length && value >= 1024; i++) {
|
|
229
|
+
value /= 1024;
|
|
230
|
+
unit = units[i];
|
|
231
|
+
}
|
|
232
|
+
return value.toFixed(value >= 10 ? 1 : 2) + ' ' + unit;
|
|
233
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export type TomlValueKind = 'table' | 'array' | 'string' | 'integer' | 'float' | 'boolean' | 'datetime';
|
|
2
|
+
/** Half-open character offsets into `TomlDocument.text`, so a host can map a
|
|
3
|
+
* node onto a text selection and a caret offset back onto a node. */
|
|
4
|
+
export interface TomlRange {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
}
|
|
2
8
|
export interface TomlNode {
|
|
3
9
|
kind: TomlValueKind;
|
|
4
10
|
key: string;
|
|
@@ -6,6 +12,10 @@ export interface TomlNode {
|
|
|
6
12
|
value?: string | boolean;
|
|
7
13
|
raw: string;
|
|
8
14
|
line: number;
|
|
15
|
+
/** `#` comments attached to the node: the run directly above it plus any
|
|
16
|
+
* trailing comment on the declaration line, `#` and padding removed. */
|
|
17
|
+
comment?: string;
|
|
18
|
+
range?: TomlRange;
|
|
9
19
|
children?: TomlNode[];
|
|
10
20
|
}
|
|
11
21
|
export interface TomlDocument {
|