mihailik commited on
Commit
480516b
·
1 Parent(s): 695b39e

It actually works (in Firefox)

Browse files
LOCAL_MODELS_README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Put downloaded Hugging Face model repo files in the `models` folder so the dev server can serve them locally.
2
+
3
+ For Gemma (example):
4
+
5
+ 1. Create directory: `models/google/gemma-2b/resolve/main/`
6
+ 2. Download and place files into that directory as they would appear on HF, e.g.:
7
+ - config.json
8
+ - tokenizer.json
9
+ - tokenizer_config.json
10
+ - pytorch_model.bin (or converted files)
11
+ - any other weights/tokenizer files
12
+
13
+ The app expects to fetch assets at paths like:
14
+
15
+ http://127.0.0.1:8812/models/google/gemma-2b/resolve/main/config.json
16
+
17
+ After placing files, reload the app and select the local Gemma entry from the slash menu. If files are present, the loader should be able to read config and proceed; if not present you'll get 404s for the missing files.
docs/0-goal.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LocalM: Project Goals & Architectural Decisions
2
+
3
+ This document outlines the problem statement, goals, and key architectural and technical policies for the `localm` project, synthesized from the project's structure and planning documents.
4
+
5
+ ## 1. Problem Statement
6
+
7
+ The core challenge is to build a web application that can **run large language models (LLMs) for text generation directly in the browser**. This presents three primary technical hurdles:
8
+
9
+ 1. **Model Compatibility**: Many models available on public hubs (like Hugging Face) are not designed for text generation (e.g., encoder-only models like BERT). Attempting to load these with a generation pipeline leads to runtime errors and a poor user experience.
10
+ 2. **UI Responsiveness**: Model discovery, loading, and inference are resource-intensive operations that can easily block the browser's main thread, causing the UI to freeze and become unresponsive.
11
+ 3. **Backend Optimization**: Multiple technologies exist for running models in the browser (e.g., Transformers.js, WebLLM), each with different performance characteristics and model support. The application needs an efficient strategy to use the best available backend for any given situation.
12
+
13
+ ## 2. Core Goals
14
+
15
+ To address the problem, the project is guided by the following goals:
16
+
17
+ 1. **Robust Model Loading**: Implement a reliable system to filter models and ensure only generation-capable architectures are loaded, thereby preventing runtime failures.
18
+ 2. **Responsive User Experience**: Guarantee a fluid UI by offloading all heavy computation (model discovery, loading, inference) to a background web worker.
19
+ 3. **Optimized Inference Performance**: Integrate multiple backends with an "optimistic fallback" strategy: attempt to use the most performant backend first (WebLLM), and seamlessly fall back to the more broadly compatible one (Transformers.js) if it fails.
20
+ 4. **Simple & Maintainable Architecture**: Prioritize simplicity and pragmatism. Avoid premature or unnecessary abstractions in favor of clear, minimal, and maintainable code that is easy to debug and evolve.
21
+
22
+ ## 3. Architectural & Technical Policies
23
+
24
+ The project adheres to several key implementation decisions that form its architectural foundation.
25
+
26
+ ### a. Worker-First Architecture
27
+
28
+ All computationally expensive tasks are delegated to a dedicated Web Worker (`src/worker/boot-worker.js`). The main UI thread remains lightweight and communicates with the worker via a dedicated connection manager (`src/app/worker-connection.js`). This is the fundamental policy for ensuring UI responsiveness.
29
+
30
+ ### b. Two-Phase Model Discovery & Filtering
31
+
32
+ A robust, multi-step process, executed entirely within the worker, is used to identify suitable chat models.
33
+
34
+ 1. **Phase 1: Heuristic Pre-filtering**: The worker first fetches a large list of model candidates from Hugging Face and applies a fast, network-free filter to eliminate obviously unsuitable models based on their metadata (e.g., `pipeline_tag`, name patterns).
35
+ 2. **Phase 2: Config-Based Classification**: For the remaining candidates, the worker fetches each model's `config.json` file. It uses the `model_type` and `architectures` fields within this file to definitively classify the model as either generation-capable (`gen`), `encoder`, or `unknown`. This is the authoritative step for ensuring compatibility.
36
+
37
+ This entire process is implemented as a modern `async` generator (`src/worker/actions/list-chat-models.js`), which streams progress back to the UI for an incremental and responsive experience.
38
+
39
+ ### c. Inline, Dual-Backend Integration
40
+
41
+ To achieve the best performance, the application integrates both WebLLM and Transformers.js using a simple, inline strategy.
42
+
43
+ - **Policy**: Optimistic Fallback.
44
+ - **Implementation**:
45
+ 1. When a model is requested, the application first attempts to load it using the high-performance **WebLLM** engine.
46
+ 2. If the WebLLM load fails for any reason (e.g., unsupported model, runtime error), the failure is logged, and the system **automatically falls back** to loading the model with the more universally compatible **Transformers.js** library.
47
+ 3. At inference time, a simple, inline check (`if`/`else`) determines which engine is backing the loaded model and routes the request accordingly.
48
+
49
+ This approach was deliberately chosen over more complex architectural patterns (like registries, adapters, or orchestrators) to minimize complexity and maintenance overhead, in alignment with the goal of architectural simplicity.
50
+
51
+ ### d. Minimal & Pragmatic Code Style
52
+
53
+ The project favors modern, concise, and readable JavaScript. It avoids complex abstractions where simple conditionals suffice and prefers clear, self-contained logic that is easy to test and debug. Exceptions are handled explicitly to either trigger a fallback or provide clear diagnostic information.
models/google/gemma-2b/resolve/main ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 9b0cfec892e2bc2afd938c98eabe4e4a7b1e0ca1
package-lock.json CHANGED
@@ -1,18 +1,18 @@
1
  {
2
  "name": "localm",
3
- "version": "1.1.38",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "localm",
9
- "version": "1.1.38",
10
  "license": "ISC",
11
  "dependencies": {
12
- "@huggingface/transformers": "^3.7.2",
13
- "@milkdown/crepe": "^7.15.3",
14
- "@mlc-ai/web-llm": "^0.2.79",
15
- "esbuild": "^0.25.9"
16
  }
17
  },
18
  "node_modules/@babel/helper-string-parser": {
@@ -34,12 +34,12 @@
34
  }
35
  },
36
  "node_modules/@babel/parser": {
37
- "version": "7.28.3",
38
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
39
- "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
40
  "license": "MIT",
41
  "dependencies": {
42
- "@babel/types": "^7.28.2"
43
  },
44
  "bin": {
45
  "parser": "bin/babel-parser.js"
@@ -49,9 +49,9 @@
49
  }
50
  },
51
  "node_modules/@babel/types": {
52
- "version": "7.28.2",
53
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
54
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
55
  "license": "MIT",
56
  "dependencies": {
57
  "@babel/helper-string-parser": "^7.27.1",
@@ -62,9 +62,9 @@
62
  }
63
  },
64
  "node_modules/@codemirror/autocomplete": {
65
- "version": "6.18.6",
66
- "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
67
- "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
68
  "license": "MIT",
69
  "dependencies": {
70
  "@codemirror/language": "^6.0.0",
@@ -136,9 +136,9 @@
136
  }
137
  },
138
  "node_modules/@codemirror/lang-html": {
139
- "version": "6.4.9",
140
- "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
141
- "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
142
  "license": "MIT",
143
  "dependencies": {
144
  "@codemirror/autocomplete": "^6.0.0",
@@ -446,9 +446,9 @@
446
  }
447
  },
448
  "node_modules/@codemirror/view": {
449
- "version": "6.38.1",
450
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
451
- "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
452
  "license": "MIT",
453
  "dependencies": {
454
  "@codemirror/state": "^6.5.0",
@@ -458,9 +458,9 @@
458
  }
459
  },
460
  "node_modules/@emnapi/runtime": {
461
- "version": "1.4.5",
462
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
463
- "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
464
  "license": "MIT",
465
  "optional": true,
466
  "dependencies": {
@@ -893,9 +893,9 @@
893
  }
894
  },
895
  "node_modules/@floating-ui/dom": {
896
- "version": "1.7.3",
897
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz",
898
- "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==",
899
  "license": "MIT",
900
  "dependencies": {
901
  "@floating-ui/core": "^1.7.3",
@@ -918,9 +918,9 @@
918
  }
919
  },
920
  "node_modules/@huggingface/transformers": {
921
- "version": "3.7.2",
922
- "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.7.2.tgz",
923
- "integrity": "sha512-6SOxo6XziupnQ5Vs5vbbs74CNB6ViHLHGQJjY6zj88JeiDtJ2d/ADKxaay688Sf2KcjtdF3dyBL11C5pJS2NxQ==",
924
  "license": "Apache-2.0",
925
  "dependencies": {
926
  "@huggingface/jinja": "^0.5.1",
@@ -1436,9 +1436,9 @@
1436
  }
1437
  },
1438
  "node_modules/@lezer/javascript": {
1439
- "version": "1.5.1",
1440
- "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
1441
- "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
1442
  "license": "MIT",
1443
  "dependencies": {
1444
  "@lezer/common": "^1.2.0",
@@ -1549,21 +1549,21 @@
1549
  "license": "MIT"
1550
  },
1551
  "node_modules/@milkdown/components": {
1552
- "version": "7.15.3",
1553
- "resolved": "https://registry.npmjs.org/@milkdown/components/-/components-7.15.3.tgz",
1554
- "integrity": "sha512-Xhfl3kV+SiUlfNbrIn4T30Q6sHFjhe7KsnydAaQsCBvfDB0Y5lin/qCWa5k/hGw+NUH6dO/GX75zQ77OOpw8dg==",
1555
  "license": "MIT",
1556
  "dependencies": {
1557
  "@floating-ui/dom": "^1.5.1",
1558
- "@milkdown/core": "7.15.3",
1559
- "@milkdown/ctx": "7.15.3",
1560
- "@milkdown/exception": "7.15.3",
1561
- "@milkdown/plugin-tooltip": "7.15.3",
1562
- "@milkdown/preset-commonmark": "7.15.3",
1563
- "@milkdown/preset-gfm": "7.15.3",
1564
- "@milkdown/prose": "7.15.3",
1565
- "@milkdown/transformer": "7.15.3",
1566
- "@milkdown/utils": "7.15.3",
1567
  "@types/lodash-es": "^4.17.12",
1568
  "clsx": "^2.0.0",
1569
  "dompurify": "^3.2.5",
@@ -1571,7 +1571,7 @@
1571
  "nanoid": "^5.0.9",
1572
  "tslib": "^2.8.1",
1573
  "unist-util-visit": "^5.0.0",
1574
- "vue": "^3.5.13"
1575
  },
1576
  "peerDependencies": {
1577
  "@codemirror/language": "^6",
@@ -1580,15 +1580,15 @@
1580
  }
1581
  },
1582
  "node_modules/@milkdown/core": {
1583
- "version": "7.15.3",
1584
- "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.15.3.tgz",
1585
- "integrity": "sha512-/sx77znzqaeRmJXkDVXNUFrodUqY6jn23FSufSa8TIGAc/IIUJIJnC5ppKUR0hdhcJInEk0sFCDeKJHjaIr+gQ==",
1586
  "license": "MIT",
1587
  "dependencies": {
1588
- "@milkdown/ctx": "7.15.3",
1589
- "@milkdown/exception": "7.15.3",
1590
- "@milkdown/prose": "7.15.3",
1591
- "@milkdown/transformer": "7.15.3",
1592
  "remark-parse": "^11.0.0",
1593
  "remark-stringify": "^11.0.0",
1594
  "tslib": "^2.8.1",
@@ -1596,9 +1596,9 @@
1596
  }
1597
  },
1598
  "node_modules/@milkdown/crepe": {
1599
- "version": "7.15.3",
1600
- "resolved": "https://registry.npmjs.org/@milkdown/crepe/-/crepe-7.15.3.tgz",
1601
- "integrity": "sha512-0YtkLObAuyfoLLKcXTkGJG3dNHxaxjuJ4Eu67T4eIkiXolK3xJRC7m137SbuejiZx9DMNUusenYThbm/bx0AWw==",
1602
  "license": "MIT",
1603
  "dependencies": {
1604
  "@codemirror/commands": "^6.2.4",
@@ -1608,7 +1608,7 @@
1608
  "@codemirror/theme-one-dark": "^6.1.2",
1609
  "@codemirror/view": "^6.16.0",
1610
  "@floating-ui/dom": "^1.5.1",
1611
- "@milkdown/kit": "7.15.3",
1612
  "@types/lodash-es": "^4.17.12",
1613
  "clsx": "^2.0.0",
1614
  "codemirror": "^6.0.1",
@@ -1619,212 +1619,212 @@
1619
  "remark-math": "^6.0.0",
1620
  "tslib": "^2.8.1",
1621
  "unist-util-visit": "^5.0.0",
1622
- "vue": "^3.5.13"
1623
  }
1624
  },
1625
  "node_modules/@milkdown/ctx": {
1626
- "version": "7.15.3",
1627
- "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.15.3.tgz",
1628
- "integrity": "sha512-bbagm7NQDOTsWTDZuMi0ZHwjx/JfZr73sKWRK66aZ4MzkKM05tUYMdfcMJJzztWNzRke9IeYRrOMXuHVpxruxw==",
1629
  "license": "MIT",
1630
  "dependencies": {
1631
- "@milkdown/exception": "7.15.3",
1632
  "tslib": "^2.8.1"
1633
  }
1634
  },
1635
  "node_modules/@milkdown/exception": {
1636
- "version": "7.15.3",
1637
- "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.15.3.tgz",
1638
- "integrity": "sha512-mj1ElCckSo2dgdaG8LL9w4lVh/tNmmZI+CQoXnREafa5rhFdxCyoh5BclX1oSUyW5j2N68Y75vEvJ0iagnL2Wg==",
1639
  "license": "MIT",
1640
  "dependencies": {
1641
  "tslib": "^2.8.1"
1642
  }
1643
  },
1644
  "node_modules/@milkdown/kit": {
1645
- "version": "7.15.3",
1646
- "resolved": "https://registry.npmjs.org/@milkdown/kit/-/kit-7.15.3.tgz",
1647
- "integrity": "sha512-TSb2LGW/o1chVpd0r+JoJPwNf5qO60s7bm+h01XCIJSbNgOL+tcY+sEBHevGN6Wji0dCRzxuibKm5HWzTdPHeA==",
1648
- "license": "MIT",
1649
- "dependencies": {
1650
- "@milkdown/components": "7.15.3",
1651
- "@milkdown/core": "7.15.3",
1652
- "@milkdown/ctx": "7.15.3",
1653
- "@milkdown/plugin-block": "7.15.3",
1654
- "@milkdown/plugin-clipboard": "7.15.3",
1655
- "@milkdown/plugin-cursor": "7.15.3",
1656
- "@milkdown/plugin-history": "7.15.3",
1657
- "@milkdown/plugin-indent": "7.15.3",
1658
- "@milkdown/plugin-listener": "7.15.3",
1659
- "@milkdown/plugin-slash": "7.15.3",
1660
- "@milkdown/plugin-tooltip": "7.15.3",
1661
- "@milkdown/plugin-trailing": "7.15.3",
1662
- "@milkdown/plugin-upload": "7.15.3",
1663
- "@milkdown/preset-commonmark": "7.15.3",
1664
- "@milkdown/preset-gfm": "7.15.3",
1665
- "@milkdown/prose": "7.15.3",
1666
- "@milkdown/transformer": "7.15.3",
1667
- "@milkdown/utils": "7.15.3",
1668
  "tslib": "^2.8.1"
1669
  }
1670
  },
1671
  "node_modules/@milkdown/plugin-block": {
1672
- "version": "7.15.3",
1673
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-block/-/plugin-block-7.15.3.tgz",
1674
- "integrity": "sha512-tk/GSEb7OVIca1otfe6yak5e33UlX+qEyMEkuuwYd618F2b6mVH22PbxqhxeL1INJ2/PoW8/s8tfe8s8/NcD5Q==",
1675
  "license": "MIT",
1676
  "dependencies": {
1677
  "@floating-ui/dom": "^1.5.1",
1678
- "@milkdown/core": "7.15.3",
1679
- "@milkdown/ctx": "7.15.3",
1680
- "@milkdown/exception": "7.15.3",
1681
- "@milkdown/prose": "7.15.3",
1682
- "@milkdown/utils": "7.15.3",
1683
  "@types/lodash-es": "^4.17.12",
1684
  "lodash-es": "^4.17.21",
1685
  "tslib": "^2.8.1"
1686
  }
1687
  },
1688
  "node_modules/@milkdown/plugin-clipboard": {
1689
- "version": "7.15.3",
1690
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-clipboard/-/plugin-clipboard-7.15.3.tgz",
1691
- "integrity": "sha512-hffS++KNC8Rw4EzDthG8a1HevCvtQut1NgLxibV3c7qbIgXSfxlH7HZiRMkXR3qVmm3fYw7UqSFRAkqhyga8yg==",
1692
  "license": "MIT",
1693
  "dependencies": {
1694
- "@milkdown/core": "7.15.3",
1695
- "@milkdown/ctx": "7.15.3",
1696
- "@milkdown/prose": "7.15.3",
1697
- "@milkdown/utils": "7.15.3",
1698
  "tslib": "^2.8.1"
1699
  }
1700
  },
1701
  "node_modules/@milkdown/plugin-cursor": {
1702
- "version": "7.15.3",
1703
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-cursor/-/plugin-cursor-7.15.3.tgz",
1704
- "integrity": "sha512-76CNXZdecE6i6hUPp89tiRCzzX6JzwidhWdmIQ18e5BKae6P8xh0GK9IGlXv5vNu3QMqJTB/4rMAuKB0utNs6Q==",
1705
  "license": "MIT",
1706
  "dependencies": {
1707
- "@milkdown/core": "7.15.3",
1708
- "@milkdown/ctx": "7.15.3",
1709
- "@milkdown/prose": "7.15.3",
1710
- "@milkdown/utils": "7.15.3",
1711
  "tslib": "^2.8.1"
1712
  }
1713
  },
1714
  "node_modules/@milkdown/plugin-history": {
1715
- "version": "7.15.3",
1716
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-history/-/plugin-history-7.15.3.tgz",
1717
- "integrity": "sha512-MkpJBpCLe9qy6LOI0/n6CQXrIpXvV/tKt49tYboCti8ru7n/MPW+3JWn57P2MAfNnBWTZUA5+sk5DbsMuQkvBQ==",
1718
  "license": "MIT",
1719
  "dependencies": {
1720
- "@milkdown/core": "7.15.3",
1721
- "@milkdown/ctx": "7.15.3",
1722
- "@milkdown/prose": "7.15.3",
1723
- "@milkdown/utils": "7.15.3",
1724
  "tslib": "^2.8.1"
1725
  }
1726
  },
1727
  "node_modules/@milkdown/plugin-indent": {
1728
- "version": "7.15.3",
1729
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-indent/-/plugin-indent-7.15.3.tgz",
1730
- "integrity": "sha512-aYMpkCDP2MA53AIZiXUWSMadWShrs/x2nonPNIvZiTtstcd5gWOtz/FqhTPKD8V1HphNdKhlEQRV89r0V4mnAw==",
1731
  "license": "MIT",
1732
  "dependencies": {
1733
- "@milkdown/core": "7.15.3",
1734
- "@milkdown/ctx": "7.15.3",
1735
- "@milkdown/prose": "7.15.3",
1736
- "@milkdown/utils": "7.15.3",
1737
  "tslib": "^2.8.1"
1738
  }
1739
  },
1740
  "node_modules/@milkdown/plugin-listener": {
1741
- "version": "7.15.3",
1742
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-listener/-/plugin-listener-7.15.3.tgz",
1743
- "integrity": "sha512-wgs6KxIkk09oMU6lhBJONVXNse1Kaz+1nCA0uCdmekKFoOOBXEL4rMzGV3CV7qkrUJFYRVVZhupmqD0gX4EPPA==",
1744
  "license": "MIT",
1745
  "dependencies": {
1746
- "@milkdown/core": "7.15.3",
1747
- "@milkdown/ctx": "7.15.3",
1748
- "@milkdown/prose": "7.15.3",
1749
- "@milkdown/utils": "7.15.3",
1750
  "@types/lodash-es": "^4.17.12",
1751
  "lodash-es": "^4.17.21",
1752
  "tslib": "^2.8.1"
1753
  }
1754
  },
1755
  "node_modules/@milkdown/plugin-slash": {
1756
- "version": "7.15.3",
1757
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-slash/-/plugin-slash-7.15.3.tgz",
1758
- "integrity": "sha512-gd9uxgboRo78Yy1SNM1Cv99wgHlk8ULiO1taY/aNd9GJmplHsmrtxHnxQwlyBxNOcWVedrqilJWq9y5dkURkeA==",
1759
  "license": "MIT",
1760
  "dependencies": {
1761
  "@floating-ui/dom": "^1.5.1",
1762
- "@milkdown/core": "7.15.3",
1763
- "@milkdown/ctx": "7.15.3",
1764
- "@milkdown/exception": "7.15.3",
1765
- "@milkdown/prose": "7.15.3",
1766
- "@milkdown/utils": "7.15.3",
1767
  "@types/lodash-es": "^4.17.12",
1768
  "lodash-es": "^4.17.21",
1769
  "tslib": "^2.8.1"
1770
  }
1771
  },
1772
  "node_modules/@milkdown/plugin-tooltip": {
1773
- "version": "7.15.3",
1774
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-tooltip/-/plugin-tooltip-7.15.3.tgz",
1775
- "integrity": "sha512-NdMRf0tnT3hYFlWccTVMCpP6gHTH34MLFGkF7xcGVzrXvA1Sb7y3WYp/T/dasc2YhHwI+/vM1X00POJEF4JwZA==",
1776
  "license": "MIT",
1777
  "dependencies": {
1778
  "@floating-ui/dom": "^1.5.1",
1779
- "@milkdown/core": "7.15.3",
1780
- "@milkdown/ctx": "7.15.3",
1781
- "@milkdown/exception": "7.15.3",
1782
- "@milkdown/prose": "7.15.3",
1783
- "@milkdown/utils": "7.15.3",
1784
  "@types/lodash-es": "^4.17.12",
1785
  "lodash-es": "^4.17.21",
1786
  "tslib": "^2.8.1"
1787
  }
1788
  },
1789
  "node_modules/@milkdown/plugin-trailing": {
1790
- "version": "7.15.3",
1791
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-trailing/-/plugin-trailing-7.15.3.tgz",
1792
- "integrity": "sha512-haNY2+LOOH/8EK4Y90vGDdzg2KFrl8xJyS/XDjEPrKP78hxAbR4fL3U9+vLSxPgtfwflWj6dgmp4mhAGR7kpQw==",
1793
  "license": "MIT",
1794
  "dependencies": {
1795
- "@milkdown/core": "7.15.3",
1796
- "@milkdown/ctx": "7.15.3",
1797
- "@milkdown/prose": "7.15.3",
1798
- "@milkdown/utils": "7.15.3",
1799
  "tslib": "^2.8.1"
1800
  }
1801
  },
1802
  "node_modules/@milkdown/plugin-upload": {
1803
- "version": "7.15.3",
1804
- "resolved": "https://registry.npmjs.org/@milkdown/plugin-upload/-/plugin-upload-7.15.3.tgz",
1805
- "integrity": "sha512-DZiDi7iWNZtJK1QnLMJ9dmCKAbAgrP1/UWtTnLg2t3kVnnvfJXCLzk/z1EekkRr3dX8saqLf3X2f14tQW5JIDQ==",
1806
  "license": "MIT",
1807
  "dependencies": {
1808
- "@milkdown/core": "7.15.3",
1809
- "@milkdown/ctx": "7.15.3",
1810
- "@milkdown/exception": "7.15.3",
1811
- "@milkdown/prose": "7.15.3",
1812
- "@milkdown/utils": "7.15.3",
1813
  "tslib": "^2.8.1"
1814
  }
1815
  },
1816
  "node_modules/@milkdown/preset-commonmark": {
1817
- "version": "7.15.3",
1818
- "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.15.3.tgz",
1819
- "integrity": "sha512-q9XDf25YgrfEmUPD7XvF9u/HaXCSVjJUvzuwsxdsf0KGt9i4G6esONTC57RZTM4e5+H9USyreL1GB3LFHMLYNw==",
1820
  "license": "MIT",
1821
  "dependencies": {
1822
- "@milkdown/core": "7.15.3",
1823
- "@milkdown/ctx": "7.15.3",
1824
- "@milkdown/exception": "7.15.3",
1825
- "@milkdown/prose": "7.15.3",
1826
- "@milkdown/transformer": "7.15.3",
1827
- "@milkdown/utils": "7.15.3",
1828
  "remark-inline-links": "^7.0.0",
1829
  "tslib": "^2.8.1",
1830
  "unist-util-visit": "^5.0.0",
@@ -1832,30 +1832,30 @@
1832
  }
1833
  },
1834
  "node_modules/@milkdown/preset-gfm": {
1835
- "version": "7.15.3",
1836
- "resolved": "https://registry.npmjs.org/@milkdown/preset-gfm/-/preset-gfm-7.15.3.tgz",
1837
- "integrity": "sha512-gsymQvoup3CyV1DsxE8cX6vhp1RhUNm1Qu7Gl+Wri9PVZwq1zBUGFoWhbA4JafeEVzv3oDE3YVYQjAQ/+UipQQ==",
1838
- "license": "MIT",
1839
- "dependencies": {
1840
- "@milkdown/core": "7.15.3",
1841
- "@milkdown/ctx": "7.15.3",
1842
- "@milkdown/exception": "7.15.3",
1843
- "@milkdown/preset-commonmark": "7.15.3",
1844
- "@milkdown/prose": "7.15.3",
1845
- "@milkdown/transformer": "7.15.3",
1846
- "@milkdown/utils": "7.15.3",
1847
  "prosemirror-safari-ime-span": "^1.0.1",
1848
  "remark-gfm": "^4.0.1",
1849
  "tslib": "^2.8.1"
1850
  }
1851
  },
1852
  "node_modules/@milkdown/prose": {
1853
- "version": "7.15.3",
1854
- "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.15.3.tgz",
1855
- "integrity": "sha512-HlHsvsoQRdT1mHJpIicYu+Kylel/Gx9Q6YkPWdG3ZVQqLGL64eN3g8qL49m7tcWMBHjPl+PYysrTIEsHpfa9+A==",
1856
  "license": "MIT",
1857
  "dependencies": {
1858
- "@milkdown/exception": "7.15.3",
1859
  "prosemirror-changeset": "^2.2.1",
1860
  "prosemirror-commands": "^1.6.2",
1861
  "prosemirror-dropcursor": "^1.8.1",
@@ -1873,13 +1873,13 @@
1873
  }
1874
  },
1875
  "node_modules/@milkdown/transformer": {
1876
- "version": "7.15.3",
1877
- "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.15.3.tgz",
1878
- "integrity": "sha512-iRLjBUjTJTkvuZ9srBa/ZT9//rlCaCdjzfD3PwueL+e9ig3q+5RTV/SX2Geiuh1ULbuJ7rPT6aXXjxB+NkWiww==",
1879
  "license": "MIT",
1880
  "dependencies": {
1881
- "@milkdown/exception": "7.15.3",
1882
- "@milkdown/prose": "7.15.3",
1883
  "remark": "^15.0.1",
1884
  "remark-parse": "^11.0.0",
1885
  "remark-stringify": "^11.0.0",
@@ -1888,16 +1888,16 @@
1888
  }
1889
  },
1890
  "node_modules/@milkdown/utils": {
1891
- "version": "7.15.3",
1892
- "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.15.3.tgz",
1893
- "integrity": "sha512-YwIEN3t77coiTMAKPduc6FiCyGuaBTNXMJeRGE3CY/ySEToiG4em3uu4am9OEA26uE5VrEjS7h1EuxVlOEGWDQ==",
1894
  "license": "MIT",
1895
  "dependencies": {
1896
- "@milkdown/core": "7.15.3",
1897
- "@milkdown/ctx": "7.15.3",
1898
- "@milkdown/exception": "7.15.3",
1899
- "@milkdown/prose": "7.15.3",
1900
- "@milkdown/transformer": "7.15.3",
1901
  "nanoid": "^5.0.9",
1902
  "tslib": "^2.8.1"
1903
  }
@@ -2030,9 +2030,9 @@
2030
  "license": "MIT"
2031
  },
2032
  "node_modules/@types/node": {
2033
- "version": "24.3.0",
2034
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
2035
- "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
2036
  "license": "MIT",
2037
  "dependencies": {
2038
  "undici-types": "~7.10.0"
@@ -2471,12 +2471,12 @@
2471
  }
2472
  },
2473
  "node_modules/escape-string-regexp": {
2474
- "version": "5.0.0",
2475
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
2476
- "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
2477
  "license": "MIT",
2478
  "engines": {
2479
- "node": ">=12"
2480
  },
2481
  "funding": {
2482
  "url": "https://github.com/sponsors/sindresorhus"
@@ -2564,9 +2564,9 @@
2564
  }
2565
  },
2566
  "node_modules/is-arrayish": {
2567
- "version": "0.3.2",
2568
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
2569
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
2570
  "license": "MIT"
2571
  },
2572
  "node_modules/is-plain-obj": {
@@ -2639,12 +2639,12 @@
2639
  }
2640
  },
2641
  "node_modules/magic-string": {
2642
- "version": "0.30.17",
2643
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
2644
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
2645
  "license": "MIT",
2646
  "dependencies": {
2647
- "@jridgewell/sourcemap-codec": "^1.5.0"
2648
  }
2649
  },
2650
  "node_modules/markdown-table": {
@@ -2669,18 +2669,6 @@
2669
  "node": ">=10"
2670
  }
2671
  },
2672
- "node_modules/matcher/node_modules/escape-string-regexp": {
2673
- "version": "4.0.0",
2674
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
2675
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
2676
- "license": "MIT",
2677
- "engines": {
2678
- "node": ">=10"
2679
- },
2680
- "funding": {
2681
- "url": "https://github.com/sponsors/sindresorhus"
2682
- }
2683
- },
2684
  "node_modules/mdast-util-definitions": {
2685
  "version": "6.0.0",
2686
  "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
@@ -2712,6 +2700,18 @@
2712
  "url": "https://opencollective.com/unified"
2713
  }
2714
  },
 
 
 
 
 
 
 
 
 
 
 
 
2715
  "node_modules/mdast-util-from-markdown": {
2716
  "version": "2.0.2",
2717
  "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
@@ -3782,9 +3782,9 @@
3782
  }
3783
  },
3784
  "node_modules/prosemirror-tables": {
3785
- "version": "1.7.1",
3786
- "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz",
3787
- "integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==",
3788
  "license": "MIT",
3789
  "dependencies": {
3790
  "prosemirror-keymap": "^1.2.2",
@@ -3804,9 +3804,9 @@
3804
  }
3805
  },
3806
  "node_modules/prosemirror-view": {
3807
- "version": "1.40.1",
3808
- "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz",
3809
- "integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==",
3810
  "license": "MIT",
3811
  "dependencies": {
3812
  "prosemirror-model": "^1.20.0",
@@ -4058,9 +4058,9 @@
4058
  }
4059
  },
4060
  "node_modules/simple-swizzle": {
4061
- "version": "0.2.2",
4062
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
4063
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
4064
  "license": "MIT",
4065
  "dependencies": {
4066
  "is-arrayish": "^0.3.1"
 
1
  {
2
  "name": "localm",
3
+ "version": "1.2.12",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "localm",
9
+ "version": "1.2.12",
10
  "license": "ISC",
11
  "dependencies": {
12
+ "@huggingface/transformers": "*",
13
+ "@milkdown/crepe": "*",
14
+ "@mlc-ai/web-llm": "*",
15
+ "esbuild": "*"
16
  }
17
  },
18
  "node_modules/@babel/helper-string-parser": {
 
34
  }
35
  },
36
  "node_modules/@babel/parser": {
37
+ "version": "7.28.4",
38
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
39
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
40
  "license": "MIT",
41
  "dependencies": {
42
+ "@babel/types": "^7.28.4"
43
  },
44
  "bin": {
45
  "parser": "bin/babel-parser.js"
 
49
  }
50
  },
51
  "node_modules/@babel/types": {
52
+ "version": "7.28.4",
53
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
54
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
55
  "license": "MIT",
56
  "dependencies": {
57
  "@babel/helper-string-parser": "^7.27.1",
 
62
  }
63
  },
64
  "node_modules/@codemirror/autocomplete": {
65
+ "version": "6.18.7",
66
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz",
67
+ "integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==",
68
  "license": "MIT",
69
  "dependencies": {
70
  "@codemirror/language": "^6.0.0",
 
136
  }
137
  },
138
  "node_modules/@codemirror/lang-html": {
139
+ "version": "6.4.10",
140
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.10.tgz",
141
+ "integrity": "sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA==",
142
  "license": "MIT",
143
  "dependencies": {
144
  "@codemirror/autocomplete": "^6.0.0",
 
446
  }
447
  },
448
  "node_modules/@codemirror/view": {
449
+ "version": "6.38.2",
450
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz",
451
+ "integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==",
452
  "license": "MIT",
453
  "dependencies": {
454
  "@codemirror/state": "^6.5.0",
 
458
  }
459
  },
460
  "node_modules/@emnapi/runtime": {
461
+ "version": "1.5.0",
462
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
463
+ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
464
  "license": "MIT",
465
  "optional": true,
466
  "dependencies": {
 
893
  }
894
  },
895
  "node_modules/@floating-ui/dom": {
896
+ "version": "1.7.4",
897
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
898
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
899
  "license": "MIT",
900
  "dependencies": {
901
  "@floating-ui/core": "^1.7.3",
 
918
  }
919
  },
920
  "node_modules/@huggingface/transformers": {
921
+ "version": "3.7.3",
922
+ "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.7.3.tgz",
923
+ "integrity": "sha512-on3Y4Pn9oK/OqNKygojnAn6RePtOVlIZbMFwnP6Q8q9p6UiYPp5IDR08KWN0FSic5fBbrZvF+vVH67vNXBqZvA==",
924
  "license": "Apache-2.0",
925
  "dependencies": {
926
  "@huggingface/jinja": "^0.5.1",
 
1436
  }
1437
  },
1438
  "node_modules/@lezer/javascript": {
1439
+ "version": "1.5.3",
1440
+ "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.3.tgz",
1441
+ "integrity": "sha512-jexmlKq5NpGiB7t+0QkyhSXRgaiab5YisHIQW9C7EcU19KSUsDguZe9WY+rmRDg34nXoNH2LQ4SxpC+aJUchSQ==",
1442
  "license": "MIT",
1443
  "dependencies": {
1444
  "@lezer/common": "^1.2.0",
 
1549
  "license": "MIT"
1550
  },
1551
  "node_modules/@milkdown/components": {
1552
+ "version": "7.15.5",
1553
+ "resolved": "https://registry.npmjs.org/@milkdown/components/-/components-7.15.5.tgz",
1554
+ "integrity": "sha512-amSlgjpp5gFKzcEUHMT6mrRGr89krlRMUo+L8MhmWBys1Wu+ZGAj5f2C2pTQncW9yVGAudquVqF+nV57aRTaoA==",
1555
  "license": "MIT",
1556
  "dependencies": {
1557
  "@floating-ui/dom": "^1.5.1",
1558
+ "@milkdown/core": "7.15.5",
1559
+ "@milkdown/ctx": "7.15.5",
1560
+ "@milkdown/exception": "7.15.5",
1561
+ "@milkdown/plugin-tooltip": "7.15.5",
1562
+ "@milkdown/preset-commonmark": "7.15.5",
1563
+ "@milkdown/preset-gfm": "7.15.5",
1564
+ "@milkdown/prose": "7.15.5",
1565
+ "@milkdown/transformer": "7.15.5",
1566
+ "@milkdown/utils": "7.15.5",
1567
  "@types/lodash-es": "^4.17.12",
1568
  "clsx": "^2.0.0",
1569
  "dompurify": "^3.2.5",
 
1571
  "nanoid": "^5.0.9",
1572
  "tslib": "^2.8.1",
1573
  "unist-util-visit": "^5.0.0",
1574
+ "vue": "3.5.18"
1575
  },
1576
  "peerDependencies": {
1577
  "@codemirror/language": "^6",
 
1580
  }
1581
  },
1582
  "node_modules/@milkdown/core": {
1583
+ "version": "7.15.5",
1584
+ "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.15.5.tgz",
1585
+ "integrity": "sha512-Pd5M/97pg2mvK+msGVG2oUFJycSDqk9RiH1D2MIaoeNDatdpDVjMv3kucBr8g6hMUsSYjlmhvMLPSZqm4EBB0A==",
1586
  "license": "MIT",
1587
  "dependencies": {
1588
+ "@milkdown/ctx": "7.15.5",
1589
+ "@milkdown/exception": "7.15.5",
1590
+ "@milkdown/prose": "7.15.5",
1591
+ "@milkdown/transformer": "7.15.5",
1592
  "remark-parse": "^11.0.0",
1593
  "remark-stringify": "^11.0.0",
1594
  "tslib": "^2.8.1",
 
1596
  }
1597
  },
1598
  "node_modules/@milkdown/crepe": {
1599
+ "version": "7.15.5",
1600
+ "resolved": "https://registry.npmjs.org/@milkdown/crepe/-/crepe-7.15.5.tgz",
1601
+ "integrity": "sha512-DTSC8Grgy4Sugze08HTsaEa1jSsU8azgzgH4f6OSg8VTVnx/mjFJz14b4Vtfc8LZbUZy0hdZM8n9Pp3Tu0nq4w==",
1602
  "license": "MIT",
1603
  "dependencies": {
1604
  "@codemirror/commands": "^6.2.4",
 
1608
  "@codemirror/theme-one-dark": "^6.1.2",
1609
  "@codemirror/view": "^6.16.0",
1610
  "@floating-ui/dom": "^1.5.1",
1611
+ "@milkdown/kit": "7.15.5",
1612
  "@types/lodash-es": "^4.17.12",
1613
  "clsx": "^2.0.0",
1614
  "codemirror": "^6.0.1",
 
1619
  "remark-math": "^6.0.0",
1620
  "tslib": "^2.8.1",
1621
  "unist-util-visit": "^5.0.0",
1622
+ "vue": "3.5.18"
1623
  }
1624
  },
1625
  "node_modules/@milkdown/ctx": {
1626
+ "version": "7.15.5",
1627
+ "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.15.5.tgz",
1628
+ "integrity": "sha512-XLNOLUYIwL/h5i8wkQbVBZELKUEa9ySLjn+5zAfomJXIWEdLs3LnXRweGF8kH0dEoKSEKcNHz+myGZO8SHPI6w==",
1629
  "license": "MIT",
1630
  "dependencies": {
1631
+ "@milkdown/exception": "7.15.5",
1632
  "tslib": "^2.8.1"
1633
  }
1634
  },
1635
  "node_modules/@milkdown/exception": {
1636
+ "version": "7.15.5",
1637
+ "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.15.5.tgz",
1638
+ "integrity": "sha512-ZU1EpM3CaklfnHlMLqoR0mVdpL6lNPz3mlOTcgOlUZGB7EOvZQlFKvsdV53hisZSo6/RfgPHBukeJdO9Wq1Jjw==",
1639
  "license": "MIT",
1640
  "dependencies": {
1641
  "tslib": "^2.8.1"
1642
  }
1643
  },
1644
  "node_modules/@milkdown/kit": {
1645
+ "version": "7.15.5",
1646
+ "resolved": "https://registry.npmjs.org/@milkdown/kit/-/kit-7.15.5.tgz",
1647
+ "integrity": "sha512-JEfBJ5lVhVNTaHYgKVfg3ztgd7O0lvgi3Kxox71VEVXa7crV3+HBr5wKkJSQrfwBm5Gr0Vk2KWj+jdPrEVtF1A==",
1648
+ "license": "MIT",
1649
+ "dependencies": {
1650
+ "@milkdown/components": "7.15.5",
1651
+ "@milkdown/core": "7.15.5",
1652
+ "@milkdown/ctx": "7.15.5",
1653
+ "@milkdown/plugin-block": "7.15.5",
1654
+ "@milkdown/plugin-clipboard": "7.15.5",
1655
+ "@milkdown/plugin-cursor": "7.15.5",
1656
+ "@milkdown/plugin-history": "7.15.5",
1657
+ "@milkdown/plugin-indent": "7.15.5",
1658
+ "@milkdown/plugin-listener": "7.15.5",
1659
+ "@milkdown/plugin-slash": "7.15.5",
1660
+ "@milkdown/plugin-tooltip": "7.15.5",
1661
+ "@milkdown/plugin-trailing": "7.15.5",
1662
+ "@milkdown/plugin-upload": "7.15.5",
1663
+ "@milkdown/preset-commonmark": "7.15.5",
1664
+ "@milkdown/preset-gfm": "7.15.5",
1665
+ "@milkdown/prose": "7.15.5",
1666
+ "@milkdown/transformer": "7.15.5",
1667
+ "@milkdown/utils": "7.15.5",
1668
  "tslib": "^2.8.1"
1669
  }
1670
  },
1671
  "node_modules/@milkdown/plugin-block": {
1672
+ "version": "7.15.5",
1673
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-block/-/plugin-block-7.15.5.tgz",
1674
+ "integrity": "sha512-g1nCWhtvCsJhDDMTk06M/jUb1M6CitGeNYAWrCj5oXV8Er8+9F5s2U1/kyAbYzjp29nC09IKSNjCsrZrpvI1Qw==",
1675
  "license": "MIT",
1676
  "dependencies": {
1677
  "@floating-ui/dom": "^1.5.1",
1678
+ "@milkdown/core": "7.15.5",
1679
+ "@milkdown/ctx": "7.15.5",
1680
+ "@milkdown/exception": "7.15.5",
1681
+ "@milkdown/prose": "7.15.5",
1682
+ "@milkdown/utils": "7.15.5",
1683
  "@types/lodash-es": "^4.17.12",
1684
  "lodash-es": "^4.17.21",
1685
  "tslib": "^2.8.1"
1686
  }
1687
  },
1688
  "node_modules/@milkdown/plugin-clipboard": {
1689
+ "version": "7.15.5",
1690
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-clipboard/-/plugin-clipboard-7.15.5.tgz",
1691
+ "integrity": "sha512-QU83bqyeThpEayDN3vKV+/7j9GtNdlSQLboElNG8ZIVp+4nFOetlNQOwJQQxv6h7j+69dnxAM7ePZ7DqnkCiDw==",
1692
  "license": "MIT",
1693
  "dependencies": {
1694
+ "@milkdown/core": "7.15.5",
1695
+ "@milkdown/ctx": "7.15.5",
1696
+ "@milkdown/prose": "7.15.5",
1697
+ "@milkdown/utils": "7.15.5",
1698
  "tslib": "^2.8.1"
1699
  }
1700
  },
1701
  "node_modules/@milkdown/plugin-cursor": {
1702
+ "version": "7.15.5",
1703
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-cursor/-/plugin-cursor-7.15.5.tgz",
1704
+ "integrity": "sha512-vtUUr+9ckv7TrSHE7P0sfna7pe0UnFIGmoj1yLh6l6WXGU48h9EBTJQfmXn+oZNgjhrW5Q4gEIvQqBftbbYtMQ==",
1705
  "license": "MIT",
1706
  "dependencies": {
1707
+ "@milkdown/core": "7.15.5",
1708
+ "@milkdown/ctx": "7.15.5",
1709
+ "@milkdown/prose": "7.15.5",
1710
+ "@milkdown/utils": "7.15.5",
1711
  "tslib": "^2.8.1"
1712
  }
1713
  },
1714
  "node_modules/@milkdown/plugin-history": {
1715
+ "version": "7.15.5",
1716
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-history/-/plugin-history-7.15.5.tgz",
1717
+ "integrity": "sha512-KigeDAUv1/VR8oRv3lIKBZIxF5s9mMCydf6BoWoc1SMFShm5ei/wEVEvex0+wlhn/BC/4xwh3K2fmNQyx6jNBw==",
1718
  "license": "MIT",
1719
  "dependencies": {
1720
+ "@milkdown/core": "7.15.5",
1721
+ "@milkdown/ctx": "7.15.5",
1722
+ "@milkdown/prose": "7.15.5",
1723
+ "@milkdown/utils": "7.15.5",
1724
  "tslib": "^2.8.1"
1725
  }
1726
  },
1727
  "node_modules/@milkdown/plugin-indent": {
1728
+ "version": "7.15.5",
1729
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-indent/-/plugin-indent-7.15.5.tgz",
1730
+ "integrity": "sha512-ikkcsY33YQ/AsYVVktmujssa7G2m7AeTUydBiCoreIWdGtyqv/ofoIpJSwxowILQ2Y/6ITo7T+YRuFFyBGPcgw==",
1731
  "license": "MIT",
1732
  "dependencies": {
1733
+ "@milkdown/core": "7.15.5",
1734
+ "@milkdown/ctx": "7.15.5",
1735
+ "@milkdown/prose": "7.15.5",
1736
+ "@milkdown/utils": "7.15.5",
1737
  "tslib": "^2.8.1"
1738
  }
1739
  },
1740
  "node_modules/@milkdown/plugin-listener": {
1741
+ "version": "7.15.5",
1742
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-listener/-/plugin-listener-7.15.5.tgz",
1743
+ "integrity": "sha512-ZGohvWL9zCKKnlgJEeiM6vBVlVvns4tEoCvM9R4IhAgD3ku/c24hK1ZRUMjtPz5yIiBW6EX6fhV+fdwjLvmIwg==",
1744
  "license": "MIT",
1745
  "dependencies": {
1746
+ "@milkdown/core": "7.15.5",
1747
+ "@milkdown/ctx": "7.15.5",
1748
+ "@milkdown/prose": "7.15.5",
1749
+ "@milkdown/utils": "7.15.5",
1750
  "@types/lodash-es": "^4.17.12",
1751
  "lodash-es": "^4.17.21",
1752
  "tslib": "^2.8.1"
1753
  }
1754
  },
1755
  "node_modules/@milkdown/plugin-slash": {
1756
+ "version": "7.15.5",
1757
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-slash/-/plugin-slash-7.15.5.tgz",
1758
+ "integrity": "sha512-iSgLz2kGVdkm5LvBjgPSPxm4xik2h5x7UlLheMbeOdK2eB5clBxJfRdrn05Xx2ZJKKworNFXcqB6as873BMxOA==",
1759
  "license": "MIT",
1760
  "dependencies": {
1761
  "@floating-ui/dom": "^1.5.1",
1762
+ "@milkdown/core": "7.15.5",
1763
+ "@milkdown/ctx": "7.15.5",
1764
+ "@milkdown/exception": "7.15.5",
1765
+ "@milkdown/prose": "7.15.5",
1766
+ "@milkdown/utils": "7.15.5",
1767
  "@types/lodash-es": "^4.17.12",
1768
  "lodash-es": "^4.17.21",
1769
  "tslib": "^2.8.1"
1770
  }
1771
  },
1772
  "node_modules/@milkdown/plugin-tooltip": {
1773
+ "version": "7.15.5",
1774
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-tooltip/-/plugin-tooltip-7.15.5.tgz",
1775
+ "integrity": "sha512-TTnub8P1mc2r0dbd1X3IsLRn5lME8hazUeAaAIUlh7iId2hbR/XtzFwYZI1q3XzWG/9YQbjI1g4wxFvqZHEcqw==",
1776
  "license": "MIT",
1777
  "dependencies": {
1778
  "@floating-ui/dom": "^1.5.1",
1779
+ "@milkdown/core": "7.15.5",
1780
+ "@milkdown/ctx": "7.15.5",
1781
+ "@milkdown/exception": "7.15.5",
1782
+ "@milkdown/prose": "7.15.5",
1783
+ "@milkdown/utils": "7.15.5",
1784
  "@types/lodash-es": "^4.17.12",
1785
  "lodash-es": "^4.17.21",
1786
  "tslib": "^2.8.1"
1787
  }
1788
  },
1789
  "node_modules/@milkdown/plugin-trailing": {
1790
+ "version": "7.15.5",
1791
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-trailing/-/plugin-trailing-7.15.5.tgz",
1792
+ "integrity": "sha512-iysHkwvOPweRut48hw6c3o1ScCTtI+Dn7lrL087peYD14afG/PfGg1mw48xwePWu5iPkTbtDpbt9OoSs7t+XRA==",
1793
  "license": "MIT",
1794
  "dependencies": {
1795
+ "@milkdown/core": "7.15.5",
1796
+ "@milkdown/ctx": "7.15.5",
1797
+ "@milkdown/prose": "7.15.5",
1798
+ "@milkdown/utils": "7.15.5",
1799
  "tslib": "^2.8.1"
1800
  }
1801
  },
1802
  "node_modules/@milkdown/plugin-upload": {
1803
+ "version": "7.15.5",
1804
+ "resolved": "https://registry.npmjs.org/@milkdown/plugin-upload/-/plugin-upload-7.15.5.tgz",
1805
+ "integrity": "sha512-rGgLGzEhpcxEYEVgNz0uwLEOqdotWroxkm/Ci96IrXp/uYVjjHFM2VuaoFTmhqs4gsVp5h2TT87n4QHMvnQKSQ==",
1806
  "license": "MIT",
1807
  "dependencies": {
1808
+ "@milkdown/core": "7.15.5",
1809
+ "@milkdown/ctx": "7.15.5",
1810
+ "@milkdown/exception": "7.15.5",
1811
+ "@milkdown/prose": "7.15.5",
1812
+ "@milkdown/utils": "7.15.5",
1813
  "tslib": "^2.8.1"
1814
  }
1815
  },
1816
  "node_modules/@milkdown/preset-commonmark": {
1817
+ "version": "7.15.5",
1818
+ "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.15.5.tgz",
1819
+ "integrity": "sha512-fE5zWdBE9QpRphR+OmdmIG28SyaGb7wQ4kz4UG696Yz9P3uxjFRYf0IKJJq8nydbhlKAEXPnmzzCKQqWa6gUIQ==",
1820
  "license": "MIT",
1821
  "dependencies": {
1822
+ "@milkdown/core": "7.15.5",
1823
+ "@milkdown/ctx": "7.15.5",
1824
+ "@milkdown/exception": "7.15.5",
1825
+ "@milkdown/prose": "7.15.5",
1826
+ "@milkdown/transformer": "7.15.5",
1827
+ "@milkdown/utils": "7.15.5",
1828
  "remark-inline-links": "^7.0.0",
1829
  "tslib": "^2.8.1",
1830
  "unist-util-visit": "^5.0.0",
 
1832
  }
1833
  },
1834
  "node_modules/@milkdown/preset-gfm": {
1835
+ "version": "7.15.5",
1836
+ "resolved": "https://registry.npmjs.org/@milkdown/preset-gfm/-/preset-gfm-7.15.5.tgz",
1837
+ "integrity": "sha512-6uZJT11C/mN7FEu1g7U8Q6E8gWmQlBj3c0XkkLpSMxQEVGGLeJr6gMC1tWLbN933mlUMU+VvyaZ4TuEfyTMz4g==",
1838
+ "license": "MIT",
1839
+ "dependencies": {
1840
+ "@milkdown/core": "7.15.5",
1841
+ "@milkdown/ctx": "7.15.5",
1842
+ "@milkdown/exception": "7.15.5",
1843
+ "@milkdown/preset-commonmark": "7.15.5",
1844
+ "@milkdown/prose": "7.15.5",
1845
+ "@milkdown/transformer": "7.15.5",
1846
+ "@milkdown/utils": "7.15.5",
1847
  "prosemirror-safari-ime-span": "^1.0.1",
1848
  "remark-gfm": "^4.0.1",
1849
  "tslib": "^2.8.1"
1850
  }
1851
  },
1852
  "node_modules/@milkdown/prose": {
1853
+ "version": "7.15.5",
1854
+ "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.15.5.tgz",
1855
+ "integrity": "sha512-1R6EIAp1ACKr1QtE+d1smZ5gIsPi6IgW0K7cngyC3JDxnNafbeVVQNpGQTkfzr0QJCHUVvPilaQ6A26inDHBhg==",
1856
  "license": "MIT",
1857
  "dependencies": {
1858
+ "@milkdown/exception": "7.15.5",
1859
  "prosemirror-changeset": "^2.2.1",
1860
  "prosemirror-commands": "^1.6.2",
1861
  "prosemirror-dropcursor": "^1.8.1",
 
1873
  }
1874
  },
1875
  "node_modules/@milkdown/transformer": {
1876
+ "version": "7.15.5",
1877
+ "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.15.5.tgz",
1878
+ "integrity": "sha512-mO2kNqBwyI9Z6PTRwSiXev1zL0XkNy7kDcHBak+RFlnLxhWAgNRQHU9DREGFGT9mLm7jmH41e1esDrR+ttrALg==",
1879
  "license": "MIT",
1880
  "dependencies": {
1881
+ "@milkdown/exception": "7.15.5",
1882
+ "@milkdown/prose": "7.15.5",
1883
  "remark": "^15.0.1",
1884
  "remark-parse": "^11.0.0",
1885
  "remark-stringify": "^11.0.0",
 
1888
  }
1889
  },
1890
  "node_modules/@milkdown/utils": {
1891
+ "version": "7.15.5",
1892
+ "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.15.5.tgz",
1893
+ "integrity": "sha512-/B5DBD9H8lPkY7jCiUEEJJUaTSrLKzLsjK5so+JxgmPJZ1E4gJjOUGnQtgzMhYZxd1Az7+vFc3XKgLY/WAzqKg==",
1894
  "license": "MIT",
1895
  "dependencies": {
1896
+ "@milkdown/core": "7.15.5",
1897
+ "@milkdown/ctx": "7.15.5",
1898
+ "@milkdown/exception": "7.15.5",
1899
+ "@milkdown/prose": "7.15.5",
1900
+ "@milkdown/transformer": "7.15.5",
1901
  "nanoid": "^5.0.9",
1902
  "tslib": "^2.8.1"
1903
  }
 
2030
  "license": "MIT"
2031
  },
2032
  "node_modules/@types/node": {
2033
+ "version": "24.3.3",
2034
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.3.tgz",
2035
+ "integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==",
2036
  "license": "MIT",
2037
  "dependencies": {
2038
  "undici-types": "~7.10.0"
 
2471
  }
2472
  },
2473
  "node_modules/escape-string-regexp": {
2474
+ "version": "4.0.0",
2475
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
2476
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
2477
  "license": "MIT",
2478
  "engines": {
2479
+ "node": ">=10"
2480
  },
2481
  "funding": {
2482
  "url": "https://github.com/sponsors/sindresorhus"
 
2564
  }
2565
  },
2566
  "node_modules/is-arrayish": {
2567
+ "version": "0.3.4",
2568
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
2569
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
2570
  "license": "MIT"
2571
  },
2572
  "node_modules/is-plain-obj": {
 
2639
  }
2640
  },
2641
  "node_modules/magic-string": {
2642
+ "version": "0.30.19",
2643
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
2644
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
2645
  "license": "MIT",
2646
  "dependencies": {
2647
+ "@jridgewell/sourcemap-codec": "^1.5.5"
2648
  }
2649
  },
2650
  "node_modules/markdown-table": {
 
2669
  "node": ">=10"
2670
  }
2671
  },
 
 
 
 
 
 
 
 
 
 
 
 
2672
  "node_modules/mdast-util-definitions": {
2673
  "version": "6.0.0",
2674
  "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
 
2700
  "url": "https://opencollective.com/unified"
2701
  }
2702
  },
2703
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
2704
+ "version": "5.0.0",
2705
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
2706
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
2707
+ "license": "MIT",
2708
+ "engines": {
2709
+ "node": ">=12"
2710
+ },
2711
+ "funding": {
2712
+ "url": "https://github.com/sponsors/sindresorhus"
2713
+ }
2714
+ },
2715
  "node_modules/mdast-util-from-markdown": {
2716
  "version": "2.0.2",
2717
  "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
 
3782
  }
3783
  },
3784
  "node_modules/prosemirror-tables": {
3785
+ "version": "1.8.1",
3786
+ "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz",
3787
+ "integrity": "sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==",
3788
  "license": "MIT",
3789
  "dependencies": {
3790
  "prosemirror-keymap": "^1.2.2",
 
3804
  }
3805
  },
3806
  "node_modules/prosemirror-view": {
3807
+ "version": "1.41.0",
3808
+ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.0.tgz",
3809
+ "integrity": "sha512-FatMIIl0vRHMcNc3sPy3cMw5MMyWuO1nWQxqvYpJvXAruucGvmQ2tyyjT2/Lbok77T9a/qZqBVCq4sj43V2ihw==",
3810
  "license": "MIT",
3811
  "dependencies": {
3812
  "prosemirror-model": "^1.20.0",
 
4058
  }
4059
  },
4060
  "node_modules/simple-swizzle": {
4061
+ "version": "0.2.4",
4062
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
4063
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
4064
  "license": "MIT",
4065
  "dependencies": {
4066
  "is-arrayish": "^0.3.1"
package.json CHANGED
@@ -20,9 +20,9 @@
20
  },
21
  "homepage": "https://github.com/oyin-bo/localm#readme",
22
  "dependencies": {
23
- "@huggingface/transformers": "^3.7.2",
24
- "@milkdown/crepe": "^7.15.3",
25
- "@mlc-ai/web-llm": "^0.2.79",
26
- "esbuild": "^0.25.9"
27
  }
28
  }
 
20
  },
21
  "homepage": "https://github.com/oyin-bo/localm#readme",
22
  "dependencies": {
23
+ "@huggingface/transformers": "*",
24
+ "@milkdown/crepe": "*",
25
+ "@mlc-ai/web-llm": "*",
26
+ "esbuild": "*"
27
  }
28
  }
src/worker/curated-model-list.json CHANGED
@@ -13,17 +13,19 @@
13
  "isTransformersJsReady": true
14
  },
15
  {
16
- "id": "mlc-ai/gemma-2-9b-it-q4f16_1-MLC",
17
- "name": "Gemma 2 9B IT q4f16",
18
  "model_type": "gemma",
19
  "architectures": ["gemma"],
20
  "classification": "gen",
21
- "confidence": "high",
22
- "size_hint": "5Gb",
23
- "fetchStatus": "ok",
24
  "hasTokenizer": true,
25
- "hasOnnxModel": true,
26
- "isTransformersJsReady": true
 
 
27
  },
28
  {
29
  "id": "Xenova/llama2.c-stories15M",
 
13
  "isTransformersJsReady": true
14
  },
15
  {
16
+ "id": "/models/google/gemma-2b/resolve/main",
17
+ "name": "Gemma 2B (local /models/google/gemma-2b/resolve/main)",
18
  "model_type": "gemma",
19
  "architectures": ["gemma"],
20
  "classification": "gen",
21
+ "confidence": "medium",
22
+ "size_hint": "~2GB",
23
+ "fetchStatus": "pending-local",
24
  "hasTokenizer": true,
25
+ "hasOnnxModel": false,
26
+ "isTransformersJsReady": false,
27
+ "requiresAuth": false,
28
+ "info": { "local_url": "/models/google/gemma-2b/resolve/main", "notes": "Place downloaded HF-style repo under ./models/google/gemma-2b so files resolve at /models/google/gemma-2b/resolve/main/..." }
29
  },
30
  {
31
  "id": "Xenova/llama2.c-stories15M",
src/worker/load-model-core.js CHANGED
@@ -14,18 +14,144 @@ export async function loadModelCore({
14
  device,
15
  onProgress
16
  }) {
17
- // Create a text-generation pipeline. Depending on the model this may
18
- // perform downloads of model weights; the library should report progress
19
- // via its own callbacks if available.
20
- const pipe = await pipeline(
21
- 'text-generation',
22
- modelName,
23
- {
24
- device,
25
- progress_callback: (progress) => {
26
- if (onProgress) onProgress(progress);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- return pipe;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
 
14
  device,
15
  onProgress
16
  }) {
17
+ // Heuristic: when modelName points at a local-served path (starts with '/' or 'http'),
18
+ // probe a few candidate base URLs to find where config/tokenizer actually live.
19
+ // This helps when assets are under `/resolve/main/` or at the repo root.
20
+ async function exists(url) {
21
+ try {
22
+ const r = await fetch(url, { method: 'HEAD' });
23
+ return r.ok;
24
+ } catch (e) {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ let chosenModelName = modelName;
30
+ try {
31
+ // Treat both '/models/owner/model' and 'models/owner/model' as local-hosted
32
+ // mirrors so we probe and later install a fetch-rewrite. Also accept http(s) URLs.
33
+ const isLocalLike = (/^\/?models\//).test(modelName) || /^https?:\/\//.test(modelName);
34
+ if (isLocalLike) {
35
+ const candidates = [];
36
+ // as-provided
37
+ candidates.push(modelName.replace(/\/$/, ''));
38
+ // if modelName doesn't end with resolve/main, try adding it
39
+ if (!/\/resolve\/main\/?$/.test(modelName)) {
40
+ candidates.push(modelName.replace(/\/$/, '') + '/resolve/main');
41
+ }
42
+ // try parent directory (strip /resolve/main)
43
+ candidates.push(modelName.replace(/\/resolve\/main\/?$/, '').replace(/\/$/, ''));
44
+
45
+ // Deduplicate while preserving order
46
+ const seen = new Set();
47
+ const uniq = candidates.filter(c => { if (seen.has(c)) return false; seen.add(c); return true; });
48
+
49
+ for (const base of uniq) {
50
+ const cfg = (base.endsWith('/')) ? base + 'config.json' : base + '/config.json';
51
+ const tokJson = (base.endsWith('/')) ? base + 'tokenizer.json' : base + '/tokenizer.json';
52
+ const tokModel = (base.endsWith('/')) ? base + 'tokenizer.model' : base + '/tokenizer.model';
53
+ const tokCfg = (base.endsWith('/')) ? base + 'tokenizer_config.json' : base + '/tokenizer_config.json';
54
+ // Probe both config and tokenizer (tokenizer may be large but HEAD is cheap)
55
+ const hasCfg = await exists(cfg);
56
+ // Accept either Hugging Face tokenizer.json or SentencePiece tokenizer.model + tokenizer_config.json
57
+ const hasTokJson = await exists(tokJson);
58
+ const hasTokModel = await exists(tokModel);
59
+ const hasTokCfg = await exists(tokCfg);
60
+ const hasTok = hasTokJson || (hasTokModel && hasTokCfg);
61
+ console.log('Probing model base', base, 'config:', hasCfg, 'tokenizer.json:', hasTokJson, 'tokenizer.model+config:', hasTokModel && hasTokCfg);
62
+ if (hasCfg && hasTok) {
63
+ chosenModelName = base.replace(/\/$/, '');
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ } catch (e) {
69
+ console.log('Local model probing failed: ', String(e));
70
+ }
71
+
72
+ // transformers.js expects a Hugging Face-style model id like 'owner/model'.
73
+ // If the user provided a local path (e.g. '/models/owner/model/resolve/main'),
74
+ // derive owner/model and pass that to pipeline, but install a fetch wrapper
75
+ // that rewrites requests to huggingface.co/<owner>/<model>/resolve/main/... -> local dev server files.
76
+ let pipelineModelId = chosenModelName;
77
+ let rewriteOwner = null;
78
+ let rewriteModel = null;
79
+ try {
80
+ // Accept both '/models/owner/model' and 'models/owner/model' forms.
81
+ if (typeof chosenModelName === 'string' && (/^\/?models\//).test(chosenModelName)) {
82
+ // Expected form: [/]<models>/<owner>/<model>[/resolve/main]
83
+ const m = chosenModelName.match(/^\/?models\/([^\/]+)\/([^\/]+)(?:\/resolve\/main)?\/?$/);
84
+ if (m) {
85
+ rewriteOwner = m[1];
86
+ rewriteModel = m[2];
87
+ pipelineModelId = `${rewriteOwner}/${rewriteModel}`;
88
  }
89
+ }
90
+ } catch (e) {
91
+ console.log('Failed to derive owner/model from local path:', String(e));
92
+ }
93
+
94
+ console.log('Creating pipeline for', pipelineModelId, 'device', device);
95
+ // Temporary fetch wrapper: if transformers.js tries to download files from
96
+ // https://huggingface.co/<owner>/<model>/resolve/main/<file>
97
+ // rewrite those requests to the local dev server path we probed earlier.
98
+ const origFetch = (typeof globalThis !== 'undefined' && globalThis.fetch) ? globalThis.fetch : null;
99
+ let wrapped = false;
100
+ if (origFetch && rewriteOwner && rewriteModel) {
101
+ try {
102
+ const localBase = pipelineModelId && typeof chosenModelName === 'string' ?
103
+ (chosenModelName.replace(/\/$/, '') + '/') : null;
104
+ globalThis.fetch = async function(input, init) {
105
+ try {
106
+ let urlStr = '';
107
+ if (typeof input === 'string') urlStr = input;
108
+ else if (input instanceof Request) urlStr = input.url;
109
+ else {
110
+ // cast to any to avoid TS complaining about unknown input shapes
111
+ try {
112
+ const anyInput = /** @type {any} */ (input);
113
+ if (anyInput && anyInput.url) urlStr = String(anyInput.url);
114
+ } catch (e) {}
115
+ }
116
+
117
+ // Match HF model asset URLs like https://huggingface.co/<owner>/<model>/resolve/main/<file>
118
+ const hfMatch = urlStr.match(new RegExp('^https?:\\/\\/huggingface\\.co\\/' + rewriteOwner + '\\/' + rewriteModel + '\\/(?:resolve\\/main\\/)?(.*)$'));
119
+ if (hfMatch && localBase) {
120
+ const filePath = hfMatch[1];
121
+ const localUrl = localBase + filePath;
122
+ return origFetch.call(this, localUrl, init);
123
+ }
124
+ } catch (e) {
125
+ // fall through to default
126
+ }
127
+ return origFetch.call(this, input, init);
128
+ };
129
+ wrapped = true;
130
+ } catch (e) {
131
+ console.log('Could not install fetch wrapper:', String(e));
132
+ }
133
+ }
134
 
135
+ try {
136
+ const pipe = await pipeline(
137
+ 'text-generation',
138
+ pipelineModelId,
139
+ {
140
+ device,
141
+ progress_callback: (progress) => {
142
+ if (onProgress) onProgress(progress);
143
+ }
144
+ });
145
+
146
+ return pipe;
147
+ } finally {
148
+ // restore original fetch
149
+ try {
150
+ if (wrapped && origFetch) {
151
+ globalThis.fetch = origFetch;
152
+ }
153
+ } catch (e) {
154
+ // ignore
155
+ }
156
+ }
157
  }
src/worker/model-cache.js CHANGED
@@ -84,10 +84,235 @@ export class ModelCache {
84
  // Try WebLLM first if probe suggests it's possible
85
  if (probe.possible) {
86
  try {
87
- const webLLMId = modelName.split('/').pop() || modelName;
88
- console.log(`Loading ${webLLMId} via WebLLM...`);
89
- const engine = await webllm.CreateMLCEngine(webLLMId, {
90
- appConfig: webllm.prebuiltAppConfig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  });
92
 
93
  // Quick end-to-end validation: run a very small prompt to ensure the
 
84
  // Try WebLLM first if probe suggests it's possible
85
  if (probe.possible) {
86
  try {
87
+ // Derive a sensible WebLLM model id (prefer owner/model when supplied)
88
+ let webLLMId = modelName;
89
+ try {
90
+ // Accept both '/models/owner/model' and 'models/owner/model' forms.
91
+ if (typeof modelName === 'string' && (/^\/?models\//).test(modelName)) {
92
+ const m = modelName.match(/^\/?models\/([^\/]+)\/([^\/]+)(?:\/resolve\/main)?\/?$/);
93
+ if (m) webLLMId = `${m[1]}/${m[2]}`;
94
+ else webLLMId = String(modelName).replace(/^\//, '');
95
+ } else if (typeof modelName === 'string' && modelName.includes('/')) {
96
+ // use as-is owner/model
97
+ webLLMId = modelName;
98
+ } else {
99
+ webLLMId = String(modelName).split('/').pop() || String(modelName);
100
+ }
101
+ } catch (e) {
102
+ webLLMId = String(modelName).split('/').pop() || String(modelName);
103
+ }
104
+
105
+ console.log(`Loading ${webLLMId} via WebLLM...`);
106
+
107
+ // The web-llm engine looks up the provided model id in appConfig.model_list.
108
+ // Many prebuilt entries use short model_ids like "gemma-2b-it-q4f16_1-MLC" rather than
109
+ // owner/model (google/gemma-2b). To increase the chance of a match, when we detect a
110
+ // local Gemma path choose a known prebuilt Gemma model_id and use it as the engine id.
111
+ let engineRequestedId = webLLMId;
112
+
113
+ // Prepare an appConfig that includes a mapping for this model id
114
+ // so WebLLM can locate the local safetensors files served by the dev server.
115
+ const baseAppConfig = webllm.prebuiltAppConfig ? JSON.parse(JSON.stringify(webllm.prebuiltAppConfig)) : {};
116
+ try {
117
+ // Ensure model_list is an array (prebuiltAppConfig uses an array)
118
+ if (!baseAppConfig.model_list) baseAppConfig.model_list = [];
119
+ // If the prebuilt appConfig already contains a gemma entry, prefer
120
+ // that exact model_id so our engineRequestedId matches what the
121
+ // runtime expects. This helps avoid mismatches from similar ids.
122
+ try {
123
+ if (Array.isArray(baseAppConfig.model_list)) {
124
+ const gemmaEntry = baseAppConfig.model_list.find(e => e && typeof e.model_id === 'string' && /gemma/i.test(e.model_id));
125
+ if (gemmaEntry && gemmaEntry.model_id) {
126
+ engineRequestedId = gemmaEntry.model_id;
127
+ }
128
+ }
129
+ } catch (e) {
130
+ // ignore
131
+ }
132
+ // Helper to push a model_list entry if it doesn't already exist
133
+ const pushModelListEntry = (entry) => {
134
+ try {
135
+ if (!Array.isArray(baseAppConfig.model_list)) baseAppConfig.model_list = [];
136
+ const exists = baseAppConfig.model_list.find(e => e && (e.model_id === entry.model_id || e.model === entry.model));
137
+ if (!exists) {
138
+ // put injected entries at the front so engine lookup sees them first
139
+ baseAppConfig.model_list.unshift(entry);
140
+ }
141
+ } catch (e) {
142
+ // ignore
143
+ }
144
+ };
145
+
146
+ // If modelName is a local-served path, derive its HTTP base
147
+ if (typeof modelName === 'string' && modelName.startsWith('/models/')) {
148
+ const origin = (typeof self !== 'undefined' && self.location && self.location.origin) ? self.location.origin : (typeof location !== 'undefined' ? location.origin : '');
149
+ // Ensure we build a stable absolute URL for the local model base.
150
+ // If modelName is a path like '/models/owner/model/resolve/main' keep the
151
+ // leading slash; if it's missing, add it. Avoid using new URL with a
152
+ // relative path that can append to the current document path and
153
+ // produce duplicated segments (e.g. '/models/models/...').
154
+ const cleaned = modelName.replace(/\/$/, '');
155
+ const withLeading = cleaned.startsWith('/') ? cleaned : '/' + cleaned.replace(/^\/*/, '');
156
+ const localBase = origin ? origin.replace(/\/$/, '') + withLeading : withLeading;
157
+ // Create entries to mirror the prebuilt Gemma manifest exactly and
158
+ // also point a local copy at our dev-server URLs. This increases
159
+ // the chance the engine will find and accept the model record.
160
+ if (/gemma-2b/i.test(localBase)) {
161
+ engineRequestedId = 'gemma-2b-it-q4f16_1-MLC';
162
+ }
163
+
164
+ // Verbatim-style prebuilt gemma entry (same shape as webllm.prebuiltAppConfig)
165
+ const prebuiltGemmaEntry = {
166
+ model: 'https://huggingface.co/mlc-ai/gemma-2-2b-it-q4f16_1-MLC',
167
+ model_id: 'gemma-2-2b-it-q4f16_1-MLC',
168
+ model_lib: 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_48/gemma-2-2b-it-q4f16_1-ctx4k_cs1k-webgpu.wasm',
169
+ vram_required_MB: 1895.3,
170
+ low_resource_required: false,
171
+ buffer_size_required_bytes: 262144000,
172
+ required_features: ['shader-f16'],
173
+ overrides: { context_window_size: 4096 }
174
+ };
175
+
176
+ pushModelListEntry(prebuiltGemmaEntry);
177
+
178
+ // Local copy that points to our served files and uses the short id
179
+ const localGemmaEntry = {
180
+ model: localBase,
181
+ model_id: engineRequestedId || prebuiltGemmaEntry.model_id,
182
+ model_lib: prebuiltGemmaEntry.model_lib,
183
+ model_type: 0,
184
+ vram_required_MB: prebuiltGemmaEntry.vram_required_MB,
185
+ buffer_size_required_bytes: prebuiltGemmaEntry.buffer_size_required_bytes,
186
+ low_resource_required: prebuiltGemmaEntry.low_resource_required,
187
+ required_features: prebuiltGemmaEntry.required_features,
188
+ overrides: prebuiltGemmaEntry.overrides,
189
+ weights: [localBase + '/model.safetensors'],
190
+ tokenizer: localBase + '/tokenizer.model',
191
+ tokenizer_config: localBase + '/tokenizer_config.json',
192
+ config: localBase + '/config.json',
193
+ format: 'safetensors',
194
+ dtype: 'fp16'
195
+ };
196
+
197
+ pushModelListEntry(localGemmaEntry);
198
+
199
+ // Also create a variant keyed by the owner/model string so engines
200
+ // that look up that id will match; the files still point locally.
201
+ const localGemmaOwnerKey = Object.assign({}, localGemmaEntry, { model_id: webLLMId });
202
+ pushModelListEntry(localGemmaOwnerKey);
203
+
204
+ // Some engine lookups are picky: they may compare against the
205
+ // model field or expect a record whose `model` value equals the
206
+ // requested id. Add a minimal mapping that uses the owner/model
207
+ // string as both `model` and `model_id` to maximize matching
208
+ // possibilities.
209
+ try {
210
+ pushModelListEntry({
211
+ model: webLLMId,
212
+ model_id: webLLMId,
213
+ model_lib: prebuiltGemmaEntry.model_lib,
214
+ model_type: 0,
215
+ vram_required_MB: prebuiltGemmaEntry.vram_required_MB,
216
+ buffer_size_required_bytes: prebuiltGemmaEntry.buffer_size_required_bytes,
217
+ low_resource_required: prebuiltGemmaEntry.low_resource_required,
218
+ required_features: prebuiltGemmaEntry.required_features,
219
+ overrides: prebuiltGemmaEntry.overrides,
220
+ weights: [localBase + '/model.safetensors'],
221
+ tokenizer: localBase + '/tokenizer.model',
222
+ tokenizer_config: localBase + '/tokenizer_config.json',
223
+ config: localBase + '/config.json',
224
+ format: 'safetensors',
225
+ dtype: 'fp16'
226
+ });
227
+ } catch (e) {
228
+ // ignore
229
+ }
230
+ } else if (typeof modelName === 'string' && modelName.includes('/')) {
231
+ // If modelName looks like owner/model (or 'models/owner/model') and we also have a local mirror
232
+ // under /models, attempt to point at that mirror (best-effort). Normalize accidental
233
+ // leading 'models' segments to avoid constructing '/models/models/...'.
234
+ const parts = modelName.split('/').filter(p => p !== '');
235
+ let owner = parts[0], model = parts[1];
236
+ // If someone passed 'models/owner/model/...' shift the window
237
+ if (owner === 'models' && parts.length >= 3) {
238
+ owner = parts[1];
239
+ model = parts[2];
240
+ }
241
+ const origin = (typeof self !== 'undefined' && self.location && self.location.origin) ? self.location.origin : (typeof location !== 'undefined' ? location.origin : '');
242
+ const probeLocal = origin ? `${origin}/models/${owner}/${model}/resolve/main` : `/models/${owner}/${model}/resolve/main`;
243
+ // If owner/model style was provided, also prefer a prebuilt gemma id when appropriate
244
+ if (/gemma-2b/i.test(probeLocal)) {
245
+ engineRequestedId = 'gemma-2b-it-q4f16_1-MLC';
246
+ }
247
+
248
+ pushModelListEntry({
249
+ model: probeLocal,
250
+ model_id: engineRequestedId,
251
+ model_lib: 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_48/gemma-2-2b-it-q4f16_1-ctx4k_cs1k-webgpu.wasm',
252
+ model_type: 0,
253
+ vram_required_MB: 1895.3,
254
+ buffer_size_required_bytes: 262144000,
255
+ low_resource_required: false,
256
+ required_features: ['shader-f16'],
257
+ overrides: { context_window_size: 4096 },
258
+ weights: [probeLocal + '/model.safetensors'],
259
+ tokenizer: probeLocal + '/tokenizer.model',
260
+ tokenizer_config: probeLocal + '/tokenizer_config.json',
261
+ config: probeLocal + '/config.json',
262
+ format: 'safetensors',
263
+ dtype: 'fp16'
264
+ });
265
+ // Also insert an entry that uses the owner/model string as model_id
266
+ // to handle engines that match against that id.
267
+ pushModelListEntry({
268
+ model: probeLocal,
269
+ model_id: webLLMId,
270
+ model_lib: 'https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_48/gemma-2-2b-it-q4f16_1-ctx4k_cs1k-webgpu.wasm',
271
+ model_type: 0,
272
+ vram_required_MB: 1895.3,
273
+ buffer_size_required_bytes: 262144000,
274
+ low_resource_required: false,
275
+ required_features: ['shader-f16'],
276
+ overrides: { context_window_size: 4096 },
277
+ weights: [probeLocal + '/model.safetensors'],
278
+ tokenizer: probeLocal + '/tokenizer.model',
279
+ tokenizer_config: probeLocal + '/tokenizer_config.json',
280
+ config: probeLocal + '/config.json',
281
+ format: 'safetensors',
282
+ dtype: 'fp16'
283
+ });
284
+ }
285
+ } catch (e) {
286
+ console.log('Failed to prepare WebLLM appConfig override:', String(e));
287
+ }
288
+
289
+ // Debug: print what appConfig we are passing so we can diagnose
290
+ // why CreateMLCEngine may not find the model record.
291
+ try {
292
+ console.log('WebLLM appConfig for', webLLMId, JSON.stringify(baseAppConfig, null, 2));
293
+ try {
294
+ // Also print a concise list of model_ids/models present to help
295
+ // quickly spot mismatches between the requested id and available
296
+ // entries.
297
+ const mappings = (Array.isArray(baseAppConfig.model_list) ? baseAppConfig.model_list : []).map(e => ({ model: e.model, model_id: e.model_id }));
298
+ console.log('WebLLM appConfig mappings:', JSON.stringify(mappings, null, 2));
299
+ } catch (e) {}
300
+ } catch (e) {
301
+ console.log('WebLLM appConfig (unserializable)');
302
+ }
303
+
304
+ // Debug: print which id we will request so we can correlate with the
305
+ // engine error message about missing model records.
306
+ try {
307
+ console.log('Requesting CreateMLCEngine with engineRequestedId=', engineRequestedId, ' webLLMId=', webLLMId);
308
+ } catch (e) {}
309
+
310
+ // Try requesting the engine with the requested model id (which may be a
311
+ // prebuilt short id like 'gemma-2b-it-q4f16_1-MLC'). The engine matches
312
+ // against appConfig.model_list entries using the model_id field, so we
313
+ // pass engineRequestedId when available to improve matching.
314
+ const engine = await webllm.CreateMLCEngine(engineRequestedId || webLLMId, {
315
+ appConfig: baseAppConfig
316
  });
317
 
318
  // Quick end-to-end validation: run a very small prompt to ensure the