doc2vec 2.15.1 → 2.17.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 +86 -1
- package/dist/content-processor.js +57 -27
- package/dist/database.js +114 -8
- package/dist/doc2vec.js +54 -3
- package/dist/eval.js +375 -0
- package/dist/ui/assets/{index-C0Yy-BZB.js → index-HvEXxtAr.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -267,6 +267,7 @@ Configuration is managed through two files:
|
|
|
267
267
|
* `qdrant_url`: (Optional) URL of your Qdrant instance. Defaults to `http://localhost:6333`.
|
|
268
268
|
* `qdrant_port`: (Optional) Port for the Qdrant REST API. Defaults to `443` if `qdrant_url` starts with `https`, otherwise `6333`.
|
|
269
269
|
* `collection_name`: (Optional) Name of the Qdrant collection to use. Defaults to `<product_name>_<version>` (lowercased, spaces replaced with underscores).
|
|
270
|
+
* `keyword_index`: (Optional) `true` to also store a BM25 keyword vector per chunk, so the MCP server can run hybrid search (`HYBRID_SEARCH=true`). Needs Qdrant 1.18 or later. See [Qdrant keyword index](#qdrant-keyword-index).
|
|
270
271
|
|
|
271
272
|
Optional embedding configuration:
|
|
272
273
|
* `embedding.provider`: Provider for embeddings (`openai` or `azure`).
|
|
@@ -485,6 +486,70 @@ The script will then:
|
|
|
485
486
|
7. Cleanup obsolete chunks.
|
|
486
487
|
8. Output detailed logs.
|
|
487
488
|
|
|
489
|
+
### Evaluating retrieval quality
|
|
490
|
+
|
|
491
|
+
`doc2vec eval` measures how well a SQLite database answers real questions, so a change to the embedding model, chunking or ranking can be judged by numbers. Each case is a question plus the URLs that should come back; the command embeds the question with the same model as the database, runs the same nearest-neighbour query as the MCP server, and reports recall@1/4/10 (share of questions with a relevant chunk in the top k) and MRR (mean reciprocal rank of the first relevant chunk).
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
doc2vec eval cases.jsonl --db ./istio.db --config config.yaml --top-k 10 --out report.json
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Cases can be `.jsonl` (one per line), `.json` (an array) or `.yaml`:
|
|
498
|
+
|
|
499
|
+
```json
|
|
500
|
+
{"id": "mtls-1", "query": "how do I enable strict mTLS for one namespace?", "relevant_urls": ["https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/"], "product_name": "istio", "version": "latest"}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
`db`, `product_name` and `version` are optional per case. URLs match ignoring `#fragments`, trailing slashes and case. `--config` reads only the `embedding` section; without it the `OPENAI_*` / `AZURE_OPENAI_*` env vars are used.
|
|
504
|
+
|
|
505
|
+
Search methods can be compared on the same database without re-ingesting it:
|
|
506
|
+
|
|
507
|
+
- `--mode dense` (default): nearest chunks by embedding, the MCP server's query.
|
|
508
|
+
- `--mode bm25`: keyword ranking with SQLite FTS5, built in memory from the stored chunk text.
|
|
509
|
+
- `--mode hybrid`: dense and BM25 lists fused with Reciprocal Rank Fusion.
|
|
510
|
+
- `--per-page`: keep only the best chunk of each page, so the top results cover more pages.
|
|
511
|
+
- `--rerank deepinfra`: rerank the top `--rerank-candidates` chunks (default 40) with `--rerank-model` (default `nvidia/Nemotron-3.5-Rerank-8B-BF16`); needs `DEEPINFRA_API_KEY`.
|
|
512
|
+
|
|
513
|
+
These match the MCP server's retrieval settings (`HYBRID_SEARCH`, `ONE_CHUNK_PER_PAGE`, `RERANKER`; see `mcp/README.md`), so a configuration can be measured before it is deployed.
|
|
514
|
+
|
|
515
|
+
Results of comparing embedding models, search methods and rerankers are in [RETRIEVAL_ROADMAP.md](RETRIEVAL_ROADMAP.md).
|
|
516
|
+
|
|
517
|
+
## Docker Images and the Browser
|
|
518
|
+
|
|
519
|
+
Website sources are rendered with a real browser (Puppeteer). The `Dockerfile` builds two images so you can choose where that browser runs:
|
|
520
|
+
|
|
521
|
+
| Target | Contents | Use it when |
|
|
522
|
+
|---|---|---|
|
|
523
|
+
| `runtime` | doc2vec only, no browser | You run a browser elsewhere and point doc2vec at it (see below). Recommended for clusters: the browser is the one component that renders untrusted input, and it can be updated on its own schedule. |
|
|
524
|
+
| `runtime-chrome` | doc2vec plus a bundled, version-matched Chrome | You want a single self-contained container. `linux/amd64` only. |
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
docker build --target runtime -t doc2vec .
|
|
528
|
+
docker build --target runtime-chrome -t doc2vec:chrome .
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Both images are based on Wolfi (Chainguard) and run as the non-root user `doc2vec` (UID 14000).
|
|
532
|
+
|
|
533
|
+
### Using a remote browser (`BROWSER_WS_ENDPOINT`)
|
|
534
|
+
|
|
535
|
+
Set `BROWSER_WS_ENDPOINT` to a Chrome DevTools Protocol WebSocket URL and doc2vec connects to that browser instead of launching one. Any CDP-speaking browser works; [browserless](https://github.com/browserless/browserless) is a convenient packaged one:
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
docker run -d --name browser -e TOKEN=secret -e TIMEOUT=0 --shm-size=1g ghcr.io/browserless/chromium
|
|
539
|
+
docker run --rm --link browser \
|
|
540
|
+
-e BROWSER_WS_ENDPOINT='ws://browser:3000?token=secret' \
|
|
541
|
+
-e OPENAI_API_KEY \
|
|
542
|
+
-v "$PWD/config.yaml:/config/config.yaml:ro" \
|
|
543
|
+
doc2vec node dist/doc2vec.js /config/config.yaml
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Notes:
|
|
547
|
+
|
|
548
|
+
- A crawl holds a browser for as long as it runs, so disable or raise the browser service's session timeout (browserless: `TIMEOUT=0`).
|
|
549
|
+
- Each connection gets its own browser session; doc2vec closes it when the crawl finishes and every 50 pages.
|
|
550
|
+
- Launch flags such as `--no-sandbox` belong to the process that starts the browser and are not sent to a remote one.
|
|
551
|
+
- With `BROWSER_WS_ENDPOINT` unset, doc2vec launches a local Chrome: `PUPPETEER_EXECUTABLE_PATH` if set, else Puppeteer's downloaded Chrome for Testing, else a system `chromium`.
|
|
552
|
+
|
|
488
553
|
## Controller Mode
|
|
489
554
|
|
|
490
555
|
Besides the one-shot sync, doc2vec can run as a **long-lived controller** that schedules sync jobs, records run history and statistics in Postgres, and serves a web UI for monitoring and managing configs:
|
|
@@ -591,12 +656,17 @@ spec:
|
|
|
591
656
|
- name: OPENAI_API_KEY
|
|
592
657
|
valueFrom:
|
|
593
658
|
secretKeyRef: { name: doc2vec-secrets, key: openai-api-key }
|
|
659
|
+
# The browser runs in its own Deployment (e.g. ghcr.io/browserless/chromium
|
|
660
|
+
# with TIMEOUT=0); see "Docker Images and the Browser". Drop this variable
|
|
661
|
+
# and use the `-chrome` image to bundle the browser instead.
|
|
662
|
+
- name: BROWSER_WS_ENDPOINT
|
|
663
|
+
value: ws://browserless:3000?token=$(BROWSERLESS_TOKEN)
|
|
594
664
|
volumeMounts:
|
|
595
665
|
- name: configs
|
|
596
666
|
mountPath: /etc/doc2vec
|
|
597
667
|
resources:
|
|
598
668
|
requests: { memory: 1Gi }
|
|
599
|
-
limits: { memory:
|
|
669
|
+
limits: { memory: 2Gi }
|
|
600
670
|
volumes:
|
|
601
671
|
- name: configs
|
|
602
672
|
configMap: { name: doc2vec-configs }
|
|
@@ -634,6 +704,21 @@ Server-sent events: `GET /api/events` (run and config updates) and `GET /api/run
|
|
|
634
704
|
* Uses `@qdrant/js-client-rest`.
|
|
635
705
|
* Requires `qdrant_url`, `qdrant_port`, `collection_name` and potentially `QDRANT_API_KEY`.
|
|
636
706
|
|
|
707
|
+
#### Qdrant point ids
|
|
708
|
+
|
|
709
|
+
Each chunk is stored as one point whose id comes from its page URL and its position on the page. Earlier versions derived the id from the chunk text, so the same text on two pages (a shared "Before you begin" section, for example) became a single point: one page lost the chunk, and both pages looked changed and were re-embedded on every sync.
|
|
710
|
+
|
|
711
|
+
After upgrading, the first sync of each **website** or **S3** source on Qdrant processes every page once (bypassing the lastmod/ETag or timestamp shortcuts) and rewrites only the pages whose chunks were merged; unchanged pages are not re-embedded. Local directories compare every file on each sync and repair themselves on the first run. GitHub issues, Zendesk and code chunks carry the issue, ticket or file in their text, so merges there are rare; any affected item is repaired the next time it changes. SQLite was never affected.
|
|
712
|
+
|
|
713
|
+
#### Qdrant keyword index
|
|
714
|
+
|
|
715
|
+
With `keyword_index: true`, each chunk is stored with two vectors: the embedding (the collection's unnamed vector, as before) and a sparse `bm25` vector that Qdrant computes from the chunk text with its built-in `qdrant/bm25` model (IDF applied at query time). The MCP server queries both when `HYBRID_SEARCH=true`.
|
|
716
|
+
|
|
717
|
+
* **New collections** are created with the `bm25` sparse vector.
|
|
718
|
+
* **Existing collections** get it added on the next sync (Qdrant 1.18+ vector schema update), and every chunk stored before then is backfilled from its stored text. Embeddings are not recomputed. The backfill only reads chunks that lack the vector, so it resumes if interrupted and costs one empty scroll once done.
|
|
719
|
+
* On a Qdrant older than 1.18 the sync logs a warning and stores chunks without the keyword vector.
|
|
720
|
+
* Turning the option off later stops writing the vector; the existing ones stay unused.
|
|
721
|
+
|
|
637
722
|
## PDF Processing
|
|
638
723
|
|
|
639
724
|
Doc2Vec includes built-in support for processing PDF files in both local directories and websites. PDF files are automatically detected by their `.pdf` extension and processed using [Mozilla's PDF.js](https://github.com/mozilla/pdf.js) library.
|
|
@@ -36,7 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.ContentProcessor = exports.BrowserLaunchError = void 0;
|
|
39
|
+
exports.ContentProcessor = exports.BROWSER_WS_ENDPOINT_ENV = exports.BrowserLaunchError = void 0;
|
|
40
|
+
exports.remoteBrowserEndpoint = remoteBrowserEndpoint;
|
|
41
|
+
exports.openBrowser = openBrowser;
|
|
40
42
|
exports.resolveBrowserExecutablePath = resolveBrowserExecutablePath;
|
|
41
43
|
const readability_1 = require("@mozilla/readability");
|
|
42
44
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -62,6 +64,56 @@ class BrowserLaunchError extends Error {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
exports.BrowserLaunchError = BrowserLaunchError;
|
|
67
|
+
/**
|
|
68
|
+
* Where the browser comes from.
|
|
69
|
+
*
|
|
70
|
+
* Unset (the default): this process launches its own Chromium, which the image
|
|
71
|
+
* has to carry. Set: this process CONNECTS to a browser running elsewhere over
|
|
72
|
+
* the Chrome DevTools Protocol, and the image needs no browser at all. The
|
|
73
|
+
* value is a full CDP WebSocket URL, e.g.
|
|
74
|
+
* ws://browserless.qdrant.svc.cluster.local:3000?token=<token>
|
|
75
|
+
*
|
|
76
|
+
* A remote browser is the one that keeps the app image small and lets the
|
|
77
|
+
* browser be updated independently of the app (a browser image tracks Chrome
|
|
78
|
+
* stable; a distro chromium is frozen at whatever the distro rebuilt last).
|
|
79
|
+
*/
|
|
80
|
+
exports.BROWSER_WS_ENDPOINT_ENV = 'BROWSER_WS_ENDPOINT';
|
|
81
|
+
function remoteBrowserEndpoint() {
|
|
82
|
+
const raw = process.env[exports.BROWSER_WS_ENDPOINT_ENV]?.trim();
|
|
83
|
+
return raw ? raw : undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Chromium flags for a browser THIS process launches. They are not sent to a
|
|
87
|
+
* remote browser: launch flags belong to the process that starts the browser.
|
|
88
|
+
*/
|
|
89
|
+
const LOCAL_LAUNCH_ARGS = [
|
|
90
|
+
'--no-sandbox',
|
|
91
|
+
'--disable-setuid-sandbox',
|
|
92
|
+
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm (critical in Docker, default 64MB)
|
|
93
|
+
'--disable-gpu',
|
|
94
|
+
'--disable-extensions',
|
|
95
|
+
];
|
|
96
|
+
const BROWSER_PROTOCOL_TIMEOUT_MS = 60000;
|
|
97
|
+
/**
|
|
98
|
+
* Open a browser: connect to the remote one named by BROWSER_WS_ENDPOINT, or
|
|
99
|
+
* launch a local one. Callers close it with browser.close() in both cases —
|
|
100
|
+
* a browserless-style service gives each connection its own browser, so
|
|
101
|
+
* closing ends only this caller's session.
|
|
102
|
+
*/
|
|
103
|
+
async function openBrowser() {
|
|
104
|
+
const endpoint = remoteBrowserEndpoint();
|
|
105
|
+
if (endpoint) {
|
|
106
|
+
return puppeteer_1.default.connect({
|
|
107
|
+
browserWSEndpoint: endpoint,
|
|
108
|
+
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return puppeteer_1.default.launch({
|
|
112
|
+
executablePath: await resolveBrowserExecutablePath(),
|
|
113
|
+
args: LOCAL_LAUNCH_ARGS,
|
|
114
|
+
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
65
117
|
/**
|
|
66
118
|
* Resolve which browser binary to launch, in order of preference:
|
|
67
119
|
* 1. PUPPETEER_EXECUTABLE_PATH (explicit override)
|
|
@@ -421,21 +473,9 @@ class ContentProcessor {
|
|
|
421
473
|
let browser = null;
|
|
422
474
|
let page = null;
|
|
423
475
|
const launchBrowser = async () => {
|
|
424
|
-
const executablePath = await resolveBrowserExecutablePath();
|
|
425
|
-
const launchOptions = {
|
|
426
|
-
executablePath,
|
|
427
|
-
args: [
|
|
428
|
-
'--no-sandbox',
|
|
429
|
-
'--disable-setuid-sandbox',
|
|
430
|
-
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm (critical in Docker, default 64MB)
|
|
431
|
-
'--disable-gpu',
|
|
432
|
-
'--disable-extensions',
|
|
433
|
-
],
|
|
434
|
-
protocolTimeout: 60000,
|
|
435
|
-
};
|
|
436
476
|
let b;
|
|
437
477
|
try {
|
|
438
|
-
b = await
|
|
478
|
+
b = await openBrowser();
|
|
439
479
|
}
|
|
440
480
|
catch (firstError) {
|
|
441
481
|
// One retry after a pause — the failure may be transient (e.g.
|
|
@@ -445,7 +485,7 @@ class ContentProcessor {
|
|
|
445
485
|
logger.warn(`Browser launch failed (${memoryDiagnostics()}), retrying once in 5s...`, firstError);
|
|
446
486
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
447
487
|
try {
|
|
448
|
-
b = await
|
|
488
|
+
b = await openBrowser();
|
|
449
489
|
}
|
|
450
490
|
catch (secondError) {
|
|
451
491
|
throw new BrowserLaunchError(`browser failed to launch twice (${memoryDiagnostics()}): ${secondError instanceof Error ? secondError.message : String(secondError)}`);
|
|
@@ -962,18 +1002,8 @@ class ContentProcessor {
|
|
|
962
1002
|
page = existingPage;
|
|
963
1003
|
}
|
|
964
1004
|
else {
|
|
965
|
-
// Standalone mode:
|
|
966
|
-
browser = await
|
|
967
|
-
executablePath: await resolveBrowserExecutablePath(),
|
|
968
|
-
args: [
|
|
969
|
-
'--no-sandbox',
|
|
970
|
-
'--disable-setuid-sandbox',
|
|
971
|
-
'--disable-dev-shm-usage',
|
|
972
|
-
'--disable-gpu',
|
|
973
|
-
'--disable-extensions',
|
|
974
|
-
],
|
|
975
|
-
protocolTimeout: 60000,
|
|
976
|
-
});
|
|
1005
|
+
// Standalone mode: open a browser for this single page
|
|
1006
|
+
browser = await openBrowser();
|
|
977
1007
|
page = await browser.newPage();
|
|
978
1008
|
}
|
|
979
1009
|
logger.debug(`Navigating to ${url}`);
|
package/dist/database.js
CHANGED
|
@@ -82,9 +82,24 @@ class DatabaseManager {
|
|
|
82
82
|
const collectionName = params.collection_name || `${config.product_name.toLowerCase().replace(/\s+/g, '_')}_${config.version}`;
|
|
83
83
|
logger.info(`Connecting to Qdrant at ${qdrantUrl}:${qdrantPort}, collection: ${collectionName}`);
|
|
84
84
|
const qdrantClient = new js_client_rest_1.QdrantClient({ url: qdrantUrl, apiKey: process.env.QDRANT_API_KEY, port: qdrantPort });
|
|
85
|
-
await this.createCollectionQdrant(qdrantClient, collectionName, logger, embeddingDimension);
|
|
85
|
+
await this.createCollectionQdrant(qdrantClient, collectionName, logger, embeddingDimension, !!params.keyword_index);
|
|
86
|
+
const connection = { client: qdrantClient, collectionName, type: 'qdrant' };
|
|
87
|
+
if (params.keyword_index) {
|
|
88
|
+
connection.keywordIndex = await this.ensureKeywordVectorQdrant(qdrantClient, collectionName, logger);
|
|
89
|
+
if (connection.keywordIndex) {
|
|
90
|
+
// Best-effort: a failed backfill must not fail the sync. New
|
|
91
|
+
// chunks still get the vector, and the next sync resumes
|
|
92
|
+
// with the chunks that lack it.
|
|
93
|
+
try {
|
|
94
|
+
await this.backfillKeywordVectorsQdrant(connection, logger);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger.warn(`Keyword vector backfill of ${collectionName} stopped early; it resumes on the next sync:`, error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
86
101
|
logger.info(`Qdrant connection established successfully`);
|
|
87
|
-
return
|
|
102
|
+
return connection;
|
|
88
103
|
}
|
|
89
104
|
else {
|
|
90
105
|
const errMsg = `Unsupported database type: ${dbConfig.type}`;
|
|
@@ -92,7 +107,71 @@ class DatabaseManager {
|
|
|
92
107
|
throw new Error(errMsg);
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
|
-
static
|
|
110
|
+
static keywordDocument(text) {
|
|
111
|
+
return { text, model: DatabaseManager.KEYWORD_MODEL };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Adds the keyword vector to a collection that predates it. Needs Qdrant
|
|
115
|
+
* 1.18+ (vector schema updates); on an older server this logs a warning and
|
|
116
|
+
* returns false, and chunks are stored without the keyword vector.
|
|
117
|
+
*/
|
|
118
|
+
static async ensureKeywordVectorQdrant(qdrantClient, collectionName, logger) {
|
|
119
|
+
try {
|
|
120
|
+
const info = await qdrantClient.getCollection(collectionName);
|
|
121
|
+
const sparse = info?.config?.params?.sparse_vectors ?? {};
|
|
122
|
+
if (sparse[DatabaseManager.KEYWORD_VECTOR]) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
logger.info(`Adding keyword vector '${DatabaseManager.KEYWORD_VECTOR}' to collection ${collectionName}`);
|
|
126
|
+
await qdrantClient.createVectorName(collectionName, DatabaseManager.KEYWORD_VECTOR, { sparse: { modifier: 'idf' } });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
logger.warn(`Could not enable the keyword index on ${collectionName} (needs Qdrant 1.18 or later); storing chunks without it:`, error);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Gives the keyword vector to every chunk stored before it existed. Only
|
|
136
|
+
* points without it are read, so an interrupted backfill resumes on the
|
|
137
|
+
* next sync and a finished one costs a single empty scroll.
|
|
138
|
+
*/
|
|
139
|
+
static async backfillKeywordVectorsQdrant(db, logger) {
|
|
140
|
+
const { client, collectionName } = db;
|
|
141
|
+
const filter = {
|
|
142
|
+
must_not: [
|
|
143
|
+
{ has_vector: DatabaseManager.KEYWORD_VECTOR },
|
|
144
|
+
{ key: 'is_metadata', match: { value: true } },
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
let updated = 0;
|
|
148
|
+
let offset = undefined;
|
|
149
|
+
do {
|
|
150
|
+
const page = await client.scroll(collectionName, {
|
|
151
|
+
filter,
|
|
152
|
+
limit: 256,
|
|
153
|
+
offset,
|
|
154
|
+
with_payload: ['content'],
|
|
155
|
+
with_vector: false,
|
|
156
|
+
});
|
|
157
|
+
const points = (page?.points ?? [])
|
|
158
|
+
.filter((p) => typeof p.payload?.content === 'string' && p.payload.content.trim().length > 0)
|
|
159
|
+
.map((p) => ({
|
|
160
|
+
id: p.id,
|
|
161
|
+
vector: { [DatabaseManager.KEYWORD_VECTOR]: DatabaseManager.keywordDocument(p.payload.content) },
|
|
162
|
+
}));
|
|
163
|
+
if (points.length > 0) {
|
|
164
|
+
await client.updateVectors(collectionName, { wait: true, points });
|
|
165
|
+
updated += points.length;
|
|
166
|
+
}
|
|
167
|
+
offset = page?.next_page_offset;
|
|
168
|
+
} while (offset !== undefined && offset !== null);
|
|
169
|
+
if (updated > 0) {
|
|
170
|
+
logger.info(`Backfilled the keyword vector for ${updated} chunks in ${collectionName}`);
|
|
171
|
+
}
|
|
172
|
+
return updated;
|
|
173
|
+
}
|
|
174
|
+
static async createCollectionQdrant(qdrantClient, collectionName, logger, embeddingDimension, keywordIndex = false) {
|
|
96
175
|
try {
|
|
97
176
|
logger.debug(`Checking if collection ${collectionName} exists`);
|
|
98
177
|
const collections = await qdrantClient.getCollections();
|
|
@@ -107,6 +186,9 @@ class DatabaseManager {
|
|
|
107
186
|
size: embeddingDimension,
|
|
108
187
|
distance: "Cosine",
|
|
109
188
|
},
|
|
189
|
+
...(keywordIndex && {
|
|
190
|
+
sparse_vectors: { [DatabaseManager.KEYWORD_VECTOR]: { modifier: 'idf' } },
|
|
191
|
+
}),
|
|
110
192
|
});
|
|
111
193
|
logger.info(`Collection ${collectionName} created successfully`);
|
|
112
194
|
}
|
|
@@ -425,23 +507,42 @@ class DatabaseManager {
|
|
|
425
507
|
});
|
|
426
508
|
transaction();
|
|
427
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* One point per position on a page. chunk_id is a hash of the chunk text,
|
|
512
|
+
* so using it as the id merged the same text on two pages (or twice on one
|
|
513
|
+
* page) into a single point: one page lost the chunk, and both looked
|
|
514
|
+
* changed on every sync and were re-embedded. Every write path deletes a
|
|
515
|
+
* page's chunks by URL before inserting the new set, so (url, chunk_index)
|
|
516
|
+
* is unique and stable enough. Points written under the old scheme keep
|
|
517
|
+
* their ids until their page changes; deletes go by URL or by the ids read
|
|
518
|
+
* back, so the two schemes coexist.
|
|
519
|
+
*/
|
|
520
|
+
static qdrantPointId(chunk) {
|
|
521
|
+
const { url, chunk_index, chunk_id } = chunk.metadata;
|
|
522
|
+
if (url && typeof chunk_index === 'number') {
|
|
523
|
+
return utils_1.Utils.hashToUuid(utils_1.Utils.generateHash(`${url}\u0000${chunk_index}`));
|
|
524
|
+
}
|
|
525
|
+
return utils_1.Utils.isValidUuid(chunk_id) ? chunk_id : utils_1.Utils.hashToUuid(chunk_id);
|
|
526
|
+
}
|
|
428
527
|
static async storeChunkInQdrant(db, chunk, embedding, chunkHash) {
|
|
429
528
|
const { client, collectionName } = db;
|
|
430
529
|
try {
|
|
431
530
|
let pointId;
|
|
432
531
|
try {
|
|
433
|
-
pointId = chunk
|
|
434
|
-
if (!utils_1.Utils.isValidUuid(pointId)) {
|
|
435
|
-
pointId = utils_1.Utils.hashToUuid(chunk.metadata.chunk_id);
|
|
436
|
-
}
|
|
532
|
+
pointId = DatabaseManager.qdrantPointId(chunk);
|
|
437
533
|
}
|
|
438
534
|
catch (e) {
|
|
439
535
|
pointId = crypto_1.default.randomUUID();
|
|
440
536
|
}
|
|
441
537
|
const hash = chunkHash || utils_1.Utils.generateHash(chunk.content);
|
|
538
|
+
// With a keyword index the point carries both vectors: the unnamed
|
|
539
|
+
// dense one (named "" in a vector map) and the BM25 one
|
|
540
|
+
const vector = db.keywordIndex
|
|
541
|
+
? { '': embedding, [DatabaseManager.KEYWORD_VECTOR]: DatabaseManager.keywordDocument(chunk.content) }
|
|
542
|
+
: embedding;
|
|
442
543
|
const pointItem = {
|
|
443
544
|
id: pointId,
|
|
444
|
-
vector
|
|
545
|
+
vector,
|
|
445
546
|
payload: {
|
|
446
547
|
content: chunk.content,
|
|
447
548
|
product_name: chunk.metadata.product_name,
|
|
@@ -868,3 +969,8 @@ class DatabaseManager {
|
|
|
868
969
|
}
|
|
869
970
|
exports.DatabaseManager = DatabaseManager;
|
|
870
971
|
DatabaseManager.columnCache = new WeakMap();
|
|
972
|
+
// Qdrant computes the BM25 keyword vector itself from the chunk text
|
|
973
|
+
// (the `qdrant/bm25` model) and applies IDF at query time (`idf` modifier).
|
|
974
|
+
// The MCP server queries the same vector name with the same model.
|
|
975
|
+
DatabaseManager.KEYWORD_VECTOR = 'bm25';
|
|
976
|
+
DatabaseManager.KEYWORD_MODEL = 'qdrant/bm25';
|
package/dist/doc2vec.js
CHANGED
|
@@ -641,8 +641,18 @@ class Doc2Vec {
|
|
|
641
641
|
// remaining pages to be skipped indefinitely.
|
|
642
642
|
const syncCompleteKey = `sync_complete:${urlPrefix}`;
|
|
643
643
|
const syncCompleteValue = await database_1.DatabaseManager.getMetadataValue(dbConnection, syncCompleteKey, undefined, logger);
|
|
644
|
-
|
|
645
|
-
|
|
644
|
+
// Qdrant point ids used to come from the chunk text, which merged the
|
|
645
|
+
// same chunk on two pages into one point (see qdrantPointId). One pass
|
|
646
|
+
// over every page rewrites the pages that lost chunks; pages whose
|
|
647
|
+
// chunks are all there are skipped by the hash comparison as usual.
|
|
648
|
+
const pointIdKey = `point_ids_by_position:${urlPrefix}`;
|
|
649
|
+
const repairPointIds = dbConnection.type === 'qdrant'
|
|
650
|
+
&& (await database_1.DatabaseManager.getMetadataValue(dbConnection, pointIdKey, undefined, logger)) !== 'true';
|
|
651
|
+
const forceFullSync = syncCompleteValue !== 'true' || repairPointIds;
|
|
652
|
+
if (forceFullSync && syncCompleteValue === 'true') {
|
|
653
|
+
logger.info('Processing all pages once to restore chunks shared between pages (per-page Qdrant point ids); unchanged pages are not re-embedded');
|
|
654
|
+
}
|
|
655
|
+
else if (forceFullSync) {
|
|
646
656
|
logger.info('Full sync has not yet completed for this source — forcing processing of all pages (bypassing lastmod/ETag skip)');
|
|
647
657
|
}
|
|
648
658
|
const crawlResult = await this.contentProcessor.crawlWebsite(config.url, config, async (url, content) => {
|
|
@@ -722,6 +732,9 @@ class Doc2Vec {
|
|
|
722
732
|
// unset and the next run will force-process all pages again.
|
|
723
733
|
if (forceFullSync) {
|
|
724
734
|
await database_1.DatabaseManager.setMetadataValue(dbConnection, syncCompleteKey, 'true', logger, this.embeddingDimension);
|
|
735
|
+
if (repairPointIds) {
|
|
736
|
+
await database_1.DatabaseManager.setMetadataValue(dbConnection, pointIdKey, 'true', logger, this.embeddingDimension);
|
|
737
|
+
}
|
|
725
738
|
logger.info('Full sync completed successfully — subsequent runs will use normal caching');
|
|
726
739
|
}
|
|
727
740
|
let removed = { items: 0, chunks: 0 };
|
|
@@ -855,7 +868,15 @@ class Doc2Vec {
|
|
|
855
868
|
const lastSyncKey = `s3_last_sync_${bucketKey}`;
|
|
856
869
|
const fileListKey = `s3_filelist_${bucketKey}`;
|
|
857
870
|
const lastSyncValue = await database_1.DatabaseManager.getMetadataValue(dbConnection, lastSyncKey, '0', logger);
|
|
858
|
-
|
|
871
|
+
// One pass over every object to restore chunks that text-derived
|
|
872
|
+
// Qdrant point ids merged across files (see processWebsite)
|
|
873
|
+
const pointIdKey = `point_ids_by_position:s3_${bucketKey}`;
|
|
874
|
+
const repairPointIds = dbConnection.type === 'qdrant'
|
|
875
|
+
&& (await database_1.DatabaseManager.getMetadataValue(dbConnection, pointIdKey, undefined, logger)) !== 'true';
|
|
876
|
+
if (repairPointIds && lastSyncValue && lastSyncValue !== '0') {
|
|
877
|
+
logger.info('Processing all objects once to restore chunks shared between files (per-page Qdrant point ids)');
|
|
878
|
+
}
|
|
879
|
+
const lastSyncTimestamp = repairPointIds ? 0 : parseInt(lastSyncValue || '0', 10);
|
|
859
880
|
const includeExtensions = config.include_extensions || ['.md', '.txt', '.html', '.htm', '.pdf', '.doc', '.docx'];
|
|
860
881
|
const excludeExtensions = config.exclude_extensions || [];
|
|
861
882
|
const encoding = config.encoding || 'utf8';
|
|
@@ -1034,6 +1055,9 @@ class Doc2Vec {
|
|
|
1034
1055
|
const currentKeys = Array.from(processedFiles);
|
|
1035
1056
|
await database_1.DatabaseManager.setMetadataValue(dbConnection, fileListKey, JSON.stringify(currentKeys), logger, this.embeddingDimension);
|
|
1036
1057
|
await database_1.DatabaseManager.setMetadataValue(dbConnection, lastSyncKey, `${syncStartTimestamp}`, logger, this.embeddingDimension);
|
|
1058
|
+
if (repairPointIds) {
|
|
1059
|
+
await database_1.DatabaseManager.setMetadataValue(dbConnection, pointIdKey, 'true', logger, this.embeddingDimension);
|
|
1060
|
+
}
|
|
1037
1061
|
logger.info(`Finished processing S3 bucket: ${config.bucket}`);
|
|
1038
1062
|
}
|
|
1039
1063
|
/**
|
|
@@ -1910,6 +1934,33 @@ if (require.main === module) {
|
|
|
1910
1934
|
.option('--source <product_name>', 'only sync sources with this product_name (repeatable)', (value, previous) => [...previous, value], [])
|
|
1911
1935
|
.option('--source-index <n>', 'only sync the source at this 0-based position in the config (repeatable, exact)', (value, previous) => [...previous, parseInt(value, 10)], [])
|
|
1912
1936
|
.action((configPath, options) => runOneShot(configPath, { names: options.source, indices: options.sourceIndex }));
|
|
1937
|
+
program
|
|
1938
|
+
.command('eval <cases>')
|
|
1939
|
+
.description('Measure retrieval quality (recall@k, MRR) of a sqlite-vec database against questions with known relevant URLs')
|
|
1940
|
+
.option('--db <path>', 'sqlite database to query (cases may override it with a "db" field)')
|
|
1941
|
+
.option('--config <path>', 'config file whose embedding section built the database (default: env vars)')
|
|
1942
|
+
.option('--top-k <n>', 'number of chunks retrieved per question', (v) => parseInt(v, 10), 10)
|
|
1943
|
+
.option('--out <path>', 'write the full JSON report here')
|
|
1944
|
+
.option('--mode <mode>', "retrieval: 'dense' (vector, as the MCP server), 'bm25' (keyword) or 'hybrid' (both, fused with RRF)", 'dense')
|
|
1945
|
+
.option('--per-page', 'keep only the best chunk of each page in the results', false)
|
|
1946
|
+
.option('--rerank <provider>', "rerank the top candidates: 'deepinfra' (needs DEEPINFRA_API_KEY)")
|
|
1947
|
+
.option('--rerank-model <model>', 'reranker model (default: nvidia/Nemotron-3.5-Rerank-8B-BF16)')
|
|
1948
|
+
.option('--rerank-candidates <n>', 'how many top chunks the reranker sees (default: 40)', (v) => parseInt(v, 10))
|
|
1949
|
+
.action(async (casesPath, options) => {
|
|
1950
|
+
// Lazy-require so the sync path doesn't load the eval module
|
|
1951
|
+
const { runEvalCommand } = require('./eval');
|
|
1952
|
+
try {
|
|
1953
|
+
await runEvalCommand({
|
|
1954
|
+
casesPath, dbPath: options.db, configPath: options.config, topK: options.topK, outPath: options.out,
|
|
1955
|
+
mode: options.mode, perPage: options.perPage,
|
|
1956
|
+
rerank: options.rerank, rerankModel: options.rerankModel, rerankCandidates: options.rerankCandidates,
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
catch (err) {
|
|
1960
|
+
console.error(err instanceof Error ? err.message : err);
|
|
1961
|
+
process.exit(1);
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1913
1964
|
program
|
|
1914
1965
|
.command('controller [configs...]')
|
|
1915
1966
|
.description('Run as a long-lived controller: schedule sync jobs per config file, persist runs in Postgres, serve a web UI')
|
package/dist/eval.js
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Retrieval evaluation: runs a set of questions with known relevant URLs
|
|
3
|
+
// against a sqlite-vec database and reports recall@k and MRR. It measures
|
|
4
|
+
// the same thing the MCP server serves (nearest chunks by distance), so a
|
|
5
|
+
// change to the embedding model, chunking or ranking can be judged by
|
|
6
|
+
// numbers instead of by eyeballing a few queries.
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.DEFAULT_K_VALUES = exports.DEFAULT_RERANK_CANDIDATES = exports.DEFAULT_RERANK_MODEL = exports.RRF_K = void 0;
|
|
42
|
+
exports.createDeepInfraReranker = createDeepInfraReranker;
|
|
43
|
+
exports.normalizeUrl = normalizeUrl;
|
|
44
|
+
exports.firstRelevantRank = firstRelevantRank;
|
|
45
|
+
exports.computeMetrics = computeMetrics;
|
|
46
|
+
exports.loadEvalCases = loadEvalCases;
|
|
47
|
+
exports.runEval = runEval;
|
|
48
|
+
exports.onePerPage = onePerPage;
|
|
49
|
+
exports.fuseRrf = fuseRrf;
|
|
50
|
+
exports.toFtsQuery = toFtsQuery;
|
|
51
|
+
exports.createSqliteRetriever = createSqliteRetriever;
|
|
52
|
+
exports.createEmbedder = createEmbedder;
|
|
53
|
+
exports.formatReport = formatReport;
|
|
54
|
+
exports.runEvalCommand = runEvalCommand;
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const path = __importStar(require("path"));
|
|
57
|
+
const yaml = __importStar(require("js-yaml"));
|
|
58
|
+
const openai_1 = require("openai");
|
|
59
|
+
exports.RRF_K = 60;
|
|
60
|
+
exports.DEFAULT_RERANK_MODEL = 'nvidia/Nemotron-3.5-Rerank-8B-BF16';
|
|
61
|
+
exports.DEFAULT_RERANK_CANDIDATES = 40;
|
|
62
|
+
// Same request the MCP server's reranker sends (mcp/src/server.ts)
|
|
63
|
+
function createDeepInfraReranker(apiKey, model = exports.DEFAULT_RERANK_MODEL) {
|
|
64
|
+
return async (query, documents) => {
|
|
65
|
+
const response = await fetch(`https://api.deepinfra.com/v1/inference/${model}`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
68
|
+
body: JSON.stringify({ queries: [query], documents }),
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`reranker returned HTTP ${response.status}: ${(await response.text()).slice(0, 200)}`);
|
|
72
|
+
}
|
|
73
|
+
const body = await response.json();
|
|
74
|
+
if (!Array.isArray(body.scores) || body.scores.length !== documents.length) {
|
|
75
|
+
throw new Error('reranker did not return one score per document');
|
|
76
|
+
}
|
|
77
|
+
return body.scores.map(Number);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
exports.DEFAULT_K_VALUES = [1, 4, 10];
|
|
81
|
+
// Treat https://x/a/, https://x/a and https://x/a#section as the same page:
|
|
82
|
+
// eval sets are written by hand and the stored URLs vary on these points.
|
|
83
|
+
function normalizeUrl(url) {
|
|
84
|
+
let normalized = url.trim();
|
|
85
|
+
const hashIndex = normalized.indexOf('#');
|
|
86
|
+
if (hashIndex !== -1)
|
|
87
|
+
normalized = normalized.substring(0, hashIndex);
|
|
88
|
+
while (normalized.length > 1 && normalized.endsWith('/'))
|
|
89
|
+
normalized = normalized.slice(0, -1);
|
|
90
|
+
return normalized.toLowerCase();
|
|
91
|
+
}
|
|
92
|
+
function firstRelevantRank(retrievedUrls, relevantUrls) {
|
|
93
|
+
const relevant = new Set(relevantUrls.map(normalizeUrl));
|
|
94
|
+
for (let i = 0; i < retrievedUrls.length; i++) {
|
|
95
|
+
const url = retrievedUrls[i];
|
|
96
|
+
if (url && relevant.has(normalizeUrl(url)))
|
|
97
|
+
return i + 1;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function computeMetrics(results, kValues = exports.DEFAULT_K_VALUES) {
|
|
102
|
+
const recall = {};
|
|
103
|
+
for (const k of kValues) {
|
|
104
|
+
const hits = results.filter(r => r.rank !== null && r.rank <= k).length;
|
|
105
|
+
recall[k] = results.length ? hits / results.length : 0;
|
|
106
|
+
}
|
|
107
|
+
const reciprocalSum = results.reduce((sum, r) => sum + (r.rank ? 1 / r.rank : 0), 0);
|
|
108
|
+
return { cases: results.length, recall, mrr: results.length ? reciprocalSum / results.length : 0 };
|
|
109
|
+
}
|
|
110
|
+
// Accepts .jsonl (one case per line), .json (an array) or .yaml/.yml (a list).
|
|
111
|
+
function loadEvalCases(filePath) {
|
|
112
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
113
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
114
|
+
let parsed;
|
|
115
|
+
if (ext === '.jsonl') {
|
|
116
|
+
parsed = raw.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('//')).map(l => JSON.parse(l));
|
|
117
|
+
}
|
|
118
|
+
else if (ext === '.json') {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
parsed = yaml.load(raw);
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(parsed)) {
|
|
125
|
+
throw new Error(`Eval file ${filePath} must contain a list of cases`);
|
|
126
|
+
}
|
|
127
|
+
return parsed.map((c, i) => {
|
|
128
|
+
if (!c || typeof c.query !== 'string' || !c.query.trim()) {
|
|
129
|
+
throw new Error(`Eval case #${i + 1} has no query`);
|
|
130
|
+
}
|
|
131
|
+
if (!Array.isArray(c.relevant_urls) || c.relevant_urls.length === 0) {
|
|
132
|
+
throw new Error(`Eval case #${i + 1} ("${c.query}") has no relevant_urls`);
|
|
133
|
+
}
|
|
134
|
+
return { ...c, id: c.id ?? String(i + 1) };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function runEval(cases, deps, options) {
|
|
138
|
+
const results = [];
|
|
139
|
+
for (const evalCase of cases) {
|
|
140
|
+
const embedding = await deps.embed(evalCase.query);
|
|
141
|
+
const chunks = await deps.retrieve(embedding, evalCase, options.topK);
|
|
142
|
+
const retrievedUrls = chunks.map(c => c.url ?? '');
|
|
143
|
+
results.push({
|
|
144
|
+
id: evalCase.id ?? '',
|
|
145
|
+
query: evalCase.query,
|
|
146
|
+
rank: firstRelevantRank(retrievedUrls, evalCase.relevant_urls),
|
|
147
|
+
retrieved_urls: retrievedUrls,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const kValues = (options.kValues ?? exports.DEFAULT_K_VALUES).filter(k => k <= options.topK);
|
|
151
|
+
return {
|
|
152
|
+
model: options.model,
|
|
153
|
+
top_k: options.topK,
|
|
154
|
+
created_at: new Date().toISOString(),
|
|
155
|
+
...computeMetrics(results, kValues),
|
|
156
|
+
results,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Keeps the first (best) chunk of each page, in order.
|
|
160
|
+
function onePerPage(chunks) {
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
return chunks.filter(chunk => {
|
|
163
|
+
const key = normalizeUrl(chunk.url ?? '');
|
|
164
|
+
if (seen.has(key))
|
|
165
|
+
return false;
|
|
166
|
+
seen.add(key);
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Reciprocal Rank Fusion: each list contributes 1 / (RRF_K + rank) per chunk.
|
|
171
|
+
// Rank-based, so dense distances and bm25 scores need no calibration.
|
|
172
|
+
function fuseRrf(lists) {
|
|
173
|
+
const scores = new Map();
|
|
174
|
+
for (const list of lists) {
|
|
175
|
+
list.forEach((chunk, index) => {
|
|
176
|
+
if (chunk.rowid === undefined)
|
|
177
|
+
return;
|
|
178
|
+
const entry = scores.get(chunk.rowid) ?? { chunk, score: 0 };
|
|
179
|
+
entry.score += 1 / (exports.RRF_K + index + 1);
|
|
180
|
+
scores.set(chunk.rowid, entry);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return [...scores.values()].sort((a, b) => b.score - a.score).map(e => e.chunk);
|
|
184
|
+
}
|
|
185
|
+
// Turns free text into an FTS5 OR-query of quoted terms, so punctuation in a
|
|
186
|
+
// question (colons, quotes, parentheses) cannot break the MATCH syntax.
|
|
187
|
+
function toFtsQuery(text) {
|
|
188
|
+
const terms = text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
189
|
+
return [...new Set(terms)].map(t => `"${t}"`).join(' OR ');
|
|
190
|
+
}
|
|
191
|
+
// Queries the database the MCP server's sqlite provider reads
|
|
192
|
+
// (mcp/src/server.ts); dense mode runs exactly its query, so the numbers
|
|
193
|
+
// reflect what an agent receives today.
|
|
194
|
+
function createSqliteRetriever(defaultDbPath, options = {}) {
|
|
195
|
+
const Database = require('better-sqlite3');
|
|
196
|
+
const sqliteVec = require('sqlite-vec');
|
|
197
|
+
const mode = options.mode ?? 'dense';
|
|
198
|
+
const connections = new Map();
|
|
199
|
+
const open = (dbPath) => {
|
|
200
|
+
let conn = connections.get(dbPath);
|
|
201
|
+
if (!conn) {
|
|
202
|
+
if (!fs.existsSync(dbPath))
|
|
203
|
+
throw new Error(`Database file not found at ${dbPath}`);
|
|
204
|
+
const db = new Database(dbPath, { readonly: true });
|
|
205
|
+
sqliteVec.load(db);
|
|
206
|
+
conn = { db };
|
|
207
|
+
connections.set(dbPath, conn);
|
|
208
|
+
}
|
|
209
|
+
return conn;
|
|
210
|
+
};
|
|
211
|
+
// The keyword index is built in memory from the stored chunk text, so an
|
|
212
|
+
// existing database can be evaluated without re-ingesting it.
|
|
213
|
+
const ftsFor = (conn) => {
|
|
214
|
+
if (!conn.fts) {
|
|
215
|
+
const fts = new Database(':memory:');
|
|
216
|
+
fts.exec(`CREATE VIRTUAL TABLE chunks USING fts5(content, url UNINDEXED, product_name UNINDEXED, version UNINDEXED)`);
|
|
217
|
+
const insert = fts.prepare('INSERT INTO chunks (rowid, content, url, product_name, version) VALUES (?, ?, ?, ?, ?)');
|
|
218
|
+
const rows = conn.db.prepare('SELECT rowid, content, url, product_name, version FROM vec_items').all();
|
|
219
|
+
fts.transaction(() => {
|
|
220
|
+
for (const r of rows)
|
|
221
|
+
insert.run(BigInt(r.rowid), r.content ?? '', r.url, r.product_name, r.version);
|
|
222
|
+
})();
|
|
223
|
+
conn.fts = fts;
|
|
224
|
+
}
|
|
225
|
+
return conn.fts;
|
|
226
|
+
};
|
|
227
|
+
const dense = (conn, queryEmbedding, evalCase, limit) => {
|
|
228
|
+
let query = `
|
|
229
|
+
SELECT rowid, url, distance, content
|
|
230
|
+
FROM vec_items
|
|
231
|
+
WHERE embedding MATCH @query_embedding`;
|
|
232
|
+
if (evalCase.product_name)
|
|
233
|
+
query += ` AND product_name = @product_name`;
|
|
234
|
+
if (evalCase.version)
|
|
235
|
+
query += ` AND version = @version`;
|
|
236
|
+
query += `
|
|
237
|
+
ORDER BY distance
|
|
238
|
+
LIMIT @top_k;`;
|
|
239
|
+
return conn.db.prepare(query).all({
|
|
240
|
+
query_embedding: new Float32Array(queryEmbedding),
|
|
241
|
+
product_name: evalCase.product_name,
|
|
242
|
+
version: evalCase.version,
|
|
243
|
+
top_k: limit,
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
const bm25 = (conn, evalCase, limit) => {
|
|
247
|
+
const match = toFtsQuery(evalCase.query);
|
|
248
|
+
if (!match)
|
|
249
|
+
return [];
|
|
250
|
+
let query = `SELECT rowid, url, content FROM chunks WHERE chunks MATCH @match`;
|
|
251
|
+
if (evalCase.product_name)
|
|
252
|
+
query += ` AND product_name = @product_name`;
|
|
253
|
+
if (evalCase.version)
|
|
254
|
+
query += ` AND version = @version`;
|
|
255
|
+
query += ` ORDER BY bm25(chunks) LIMIT @limit`;
|
|
256
|
+
return ftsFor(conn).prepare(query).all({
|
|
257
|
+
match,
|
|
258
|
+
product_name: evalCase.product_name,
|
|
259
|
+
version: evalCase.version,
|
|
260
|
+
limit,
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
return async (queryEmbedding, evalCase, topK) => {
|
|
264
|
+
const dbPath = evalCase.db ?? defaultDbPath;
|
|
265
|
+
if (!dbPath)
|
|
266
|
+
throw new Error(`No database for eval case ${evalCase.id}: pass --db or set "db" on the case`);
|
|
267
|
+
const conn = open(dbPath);
|
|
268
|
+
const rerankCandidates = options.rerankCandidates ?? exports.DEFAULT_RERANK_CANDIDATES;
|
|
269
|
+
const depth = Math.max(topK, options.candidates ?? (mode === 'dense' && !options.perPage ? topK : 50), options.rerank ? rerankCandidates : 0);
|
|
270
|
+
let chunks;
|
|
271
|
+
if (mode === 'bm25') {
|
|
272
|
+
chunks = bm25(conn, evalCase, depth);
|
|
273
|
+
}
|
|
274
|
+
else if (mode === 'hybrid') {
|
|
275
|
+
chunks = fuseRrf([dense(conn, queryEmbedding, evalCase, depth), bm25(conn, evalCase, depth)]);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
chunks = dense(conn, queryEmbedding, evalCase, depth);
|
|
279
|
+
}
|
|
280
|
+
if (options.rerank && chunks.length > 1) {
|
|
281
|
+
const pool = chunks.slice(0, rerankCandidates);
|
|
282
|
+
const scores = await options.rerank(evalCase.query, pool.map(c => c.content ?? ''));
|
|
283
|
+
const reranked = pool.map((c, i) => ({ c, s: scores[i] })).sort((a, b) => b.s - a.s).map(x => x.c);
|
|
284
|
+
chunks = [...reranked, ...chunks.slice(rerankCandidates)];
|
|
285
|
+
}
|
|
286
|
+
if (options.perPage)
|
|
287
|
+
chunks = onePerPage(chunks);
|
|
288
|
+
return chunks.slice(0, topK);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// Mirrors the provider selection in Doc2Vec's constructor and the MCP server's
|
|
292
|
+
// env vars, so the query is embedded with the model the database was built with.
|
|
293
|
+
function createEmbedder(embeddingConfig) {
|
|
294
|
+
const provider = embeddingConfig?.provider || process.env.EMBEDDING_PROVIDER || 'openai';
|
|
295
|
+
let client;
|
|
296
|
+
let model;
|
|
297
|
+
if (provider === 'azure') {
|
|
298
|
+
const apiKey = embeddingConfig?.azure?.api_key || process.env.AZURE_OPENAI_KEY;
|
|
299
|
+
const endpoint = embeddingConfig?.azure?.endpoint || process.env.AZURE_OPENAI_ENDPOINT;
|
|
300
|
+
model = embeddingConfig?.azure?.deployment_name || process.env.AZURE_OPENAI_DEPLOYMENT_NAME || 'text-embedding-3-large';
|
|
301
|
+
if (!apiKey || !endpoint)
|
|
302
|
+
throw new Error('Azure OpenAI requires api_key and endpoint to be configured');
|
|
303
|
+
client = new openai_1.AzureOpenAI({
|
|
304
|
+
apiKey,
|
|
305
|
+
endpoint,
|
|
306
|
+
deployment: model,
|
|
307
|
+
apiVersion: embeddingConfig?.azure?.api_version || process.env.AZURE_OPENAI_API_VERSION || '2024-10-21',
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const apiKey = embeddingConfig?.openai?.api_key || process.env.OPENAI_API_KEY;
|
|
312
|
+
const baseURL = embeddingConfig?.openai?.base_url || process.env.OPENAI_BASE_URL;
|
|
313
|
+
model = embeddingConfig?.openai?.model || process.env.OPENAI_MODEL || 'text-embedding-3-large';
|
|
314
|
+
if (!apiKey)
|
|
315
|
+
throw new Error('OpenAI requires api_key to be configured');
|
|
316
|
+
client = new openai_1.OpenAI({ apiKey, ...(baseURL && { baseURL }) });
|
|
317
|
+
}
|
|
318
|
+
const embed = async (text) => {
|
|
319
|
+
const response = await client.embeddings.create({ model, input: text });
|
|
320
|
+
return response.data[0].embedding;
|
|
321
|
+
};
|
|
322
|
+
return { embed, model };
|
|
323
|
+
}
|
|
324
|
+
function formatReport(report) {
|
|
325
|
+
const lines = [];
|
|
326
|
+
lines.push(`Model: ${report.model} Cases: ${report.cases} top_k: ${report.top_k}`);
|
|
327
|
+
const recallParts = Object.entries(report.recall).map(([k, v]) => `recall@${k} ${(v * 100).toFixed(1)}%`);
|
|
328
|
+
lines.push(`${recallParts.join(' ')} MRR ${report.mrr.toFixed(3)}`);
|
|
329
|
+
const misses = report.results.filter(r => r.rank === null);
|
|
330
|
+
if (misses.length) {
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push(`Misses (${misses.length}):`);
|
|
333
|
+
for (const m of misses)
|
|
334
|
+
lines.push(` [${m.id}] ${m.query}`);
|
|
335
|
+
}
|
|
336
|
+
return lines.join('\n');
|
|
337
|
+
}
|
|
338
|
+
async function runEvalCommand(options) {
|
|
339
|
+
const mode = options.mode ?? 'dense';
|
|
340
|
+
if (!['dense', 'bm25', 'hybrid'].includes(mode)) {
|
|
341
|
+
throw new Error(`Unknown mode '${mode}': use dense, bm25 or hybrid`);
|
|
342
|
+
}
|
|
343
|
+
let rerank;
|
|
344
|
+
if (options.rerank) {
|
|
345
|
+
if (options.rerank !== 'deepinfra')
|
|
346
|
+
throw new Error(`Unknown reranker '${options.rerank}': use deepinfra`);
|
|
347
|
+
const apiKey = process.env.DEEPINFRA_API_KEY;
|
|
348
|
+
if (!apiKey)
|
|
349
|
+
throw new Error('--rerank deepinfra requires the DEEPINFRA_API_KEY environment variable');
|
|
350
|
+
rerank = createDeepInfraReranker(apiKey, options.rerankModel);
|
|
351
|
+
}
|
|
352
|
+
let embeddingConfig;
|
|
353
|
+
if (options.configPath) {
|
|
354
|
+
const config = yaml.load(fs.readFileSync(options.configPath, 'utf8'));
|
|
355
|
+
embeddingConfig = config?.embedding;
|
|
356
|
+
}
|
|
357
|
+
// Keyword-only retrieval never looks at the query vector, so skip the API call
|
|
358
|
+
const { embed, model } = mode === 'bm25'
|
|
359
|
+
? { embed: async () => [], model: 'none (bm25)' }
|
|
360
|
+
: createEmbedder(embeddingConfig);
|
|
361
|
+
const cases = loadEvalCases(options.casesPath);
|
|
362
|
+
const retrieve = createSqliteRetriever(options.dbPath, {
|
|
363
|
+
mode, perPage: options.perPage, rerank, rerankCandidates: options.rerankCandidates,
|
|
364
|
+
});
|
|
365
|
+
const label = `${model}, ${mode}` +
|
|
366
|
+
`${rerank ? `, reranked by ${options.rerankModel ?? exports.DEFAULT_RERANK_MODEL} over ${options.rerankCandidates ?? exports.DEFAULT_RERANK_CANDIDATES}` : ''}` +
|
|
367
|
+
`${options.perPage ? ', one chunk per page' : ''}`;
|
|
368
|
+
const report = await runEval(cases, { embed, retrieve }, { topK: options.topK, model: label });
|
|
369
|
+
console.log(formatReport(report));
|
|
370
|
+
if (options.outPath) {
|
|
371
|
+
fs.writeFileSync(options.outPath, JSON.stringify(report, null, 2));
|
|
372
|
+
console.log(`\nReport written to ${options.outPath}`);
|
|
373
|
+
}
|
|
374
|
+
return report;
|
|
375
|
+
}
|
|
@@ -52,7 +52,7 @@ ${e.slice(l+1)}`:c+=e.slice(i),c.slice(1)}function p7(e){let t="",n=0;for(let r=
|
|
|
52
52
|
`:" ";r+=`---${p}${d}
|
|
53
53
|
`}else r+=Ba(n,0,l.contents,{block:!0,compact:!0})+`
|
|
54
54
|
`;i=l.explicitEnd||l.contents!==null&&b7(l.contents),i&&(r+=`...
|
|
55
|
-
`)}return r}var w7={...AO,schema:YW,skipInvalid:!1,noRefs:!1,flowLevel:-1,transform:()=>{}};function O7(e,t={}){const n={...w7,...t},r=HV(e,n.schema,{noRefs:n.noRefs,skipInvalid:n.skipInvalid});return n.flowLevel>=0&&qV(r,(i,a)=>{if(!(a.depth<n.flowLevel))return i.style.flow=!0,tI}),n.transform(r),S7(r,{...W1(n,Object.keys(AO)),schema:n.schema})}const HM=[{key:"product_name",label:"Product name",type:"string",required:!0,placeholder:"istio",help:"Stored in chunk metadata; used to identify the source"},{key:"version",label:"Version",type:"string",required:!0,placeholder:"latest",help:"Required for all types except code (defaults to branch)"},{key:"max_size",label:"Max page size (bytes)",type:"number",placeholder:"1048576",help:"Pages/files larger than this are skipped"}],Fp={website:{label:"Website",fields:[{key:"url",label:"Base URL",type:"string",required:!0,placeholder:"https://docs.example.com/"},{key:"sitemap_url",label:"Sitemap URL",type:"string",placeholder:"https://docs.example.com/sitemap.xml",help:"Discovers pages not linked in navigation; lastmod dates enable cheap change detection"},{key:"markdown_store",label:"Store markdown in Postgres",type:"boolean",help:"Requires the top-level markdown store to be configured"}]},github:{label:"GitHub issues",fields:[{key:"repo",label:"Repository",type:"string",required:!0,placeholder:"owner/repo"},{key:"start_date",label:"Start date",type:"string",placeholder:"2025-01-01",help:"Only issues updated after this date (first run)"}]},local_directory:{label:"Local directory",fields:[{key:"path",label:"Directory path",type:"string",required:!0,placeholder:"/data/docs"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".md, .txt, .pdf"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list",placeholder:".log, .tmp"},{key:"recursive",label:"Recurse into subdirectories",type:"boolean"},{key:"encoding",label:"File encoding",type:"string",placeholder:"utf8"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string",placeholder:"https://mydomain.com",help:"Rewrites file:// URLs in chunk metadata"}]},zendesk:{label:"Zendesk",fields:[{key:"zendesk_subdomain",label:"Subdomain",type:"string",required:!0,placeholder:"mycompany",help:"mycompany.zendesk.com"},{key:"email",label:"User email",type:"string",required:!0,placeholder:"agent@company.com"},{key:"api_token",label:"API token",type:"string",required:!0,secret:!0,placeholder:"${ZENDESK_API_TOKEN}"},{key:"fetch_tickets",label:"Fetch tickets",type:"boolean"},{key:"fetch_articles",label:"Fetch help center articles",type:"boolean"},{key:"start_date",label:"Start date",type:"string",placeholder:"2025-01-01"},{key:"ticket_status",label:"Ticket statuses",type:"string_list",placeholder:"new, open, pending, hold, solved"},{key:"ticket_priority",label:"Ticket priorities",type:"string_list",placeholder:"urgent, high"},{key:"excluded_organizations",label:"Excluded organizations",type:"string_list"},{key:"include_internal_comments",label:"Include internal comments",type:"boolean"}]},code:{label:"Code",fields:[{key:"source",label:"Code source",type:"select",required:!0,options:["github","local_directory"]},{key:"repo",label:"Repository",type:"string",placeholder:"owner/repo",help:"When source is github"},{key:"branch",label:"Branch",type:"string",placeholder:"main",help:"When source is github"},{key:"path",label:"Directory path",type:"string",placeholder:"/data/src",help:"When source is local_directory"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".ts, .go, .py"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list"},{key:"exclude_paths",label:"Exclude paths",type:"string_list",placeholder:"vendor/**, **/*_test.go",help:"Glob patterns relative to the source root"},{key:"recursive",label:"Recurse into subdirectories",type:"boolean"},{key:"chunk_size",label:"Chunk size (tokens)",type:"number",placeholder:"1500"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string"}]},s3:{label:"S3 bucket",fields:[{key:"bucket",label:"Bucket",type:"string",required:!0,placeholder:"my-docs-bucket"},{key:"prefix",label:"Key prefix",type:"string",placeholder:"docs/"},{key:"region",label:"Region",type:"string",placeholder:"us-east-1"},{key:"endpoint",label:"Custom endpoint",type:"string",placeholder:"https://minio.local:9000",help:"For S3-compatible services (MinIO, LocalStack)"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".md, .txt, .pdf"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list"},{key:"encoding",label:"Text encoding",type:"string",placeholder:"utf8"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string",help:"Rewrites s3:// URLs in chunk metadata"}]}},vx={sqlite:{label:"SQLite (sqlite-vec)",fields:[{key:"db_path",label:"Database file",type:"string",placeholder:"./my-product.db"}]},qdrant:{label:"Qdrant",fields:[{key:"qdrant_url",label:"Qdrant URL",type:"string",placeholder:"http://localhost"},{key:"qdrant_port",label:"Port",type:"number",placeholder:"6333"},{key:"collection_name",label:"Collection",type:"string",placeholder:"my-product"}]}},FM={openai:{label:"OpenAI (or compatible)",fields:[{key:"api_key",label:"API key",type:"string",secret:!0,placeholder:"${OPENAI_API_KEY}",help:"Falls back to the OPENAI_API_KEY env var"},{key:"model",label:"Model",type:"string",placeholder:"text-embedding-3-large"},{key:"base_url",label:"Base URL",type:"string",placeholder:"http://localhost:11434/v1",help:"For Ollama or other OpenAI-compatible endpoints"}]},azure:{label:"Azure OpenAI",fields:[{key:"api_key",label:"API key",type:"string",secret:!0,placeholder:"${AZURE_OPENAI_KEY}"},{key:"endpoint",label:"Endpoint",type:"string",placeholder:"https://myresource.openai.azure.com"},{key:"deployment_name",label:"Deployment name",type:"string",placeholder:"text-embedding-3-large"},{key:"api_version",label:"API version",type:"string",placeholder:"2024-10-21"}]}},A7=[{key:"connection_string",label:"Connection string",type:"string",secret:!0,placeholder:"postgres://user:${PG_PASSWORD}@host:5432/db",help:"Either this, or the discrete fields below"},{key:"host",label:"Host",type:"string"},{key:"port",label:"Port",type:"number",placeholder:"5432"},{key:"database",label:"Database",type:"string"},{key:"user",label:"User",type:"string"},{key:"password",label:"Password",type:"string",secret:!0,placeholder:"${PG_PASSWORD}"},{key:"table_name",label:"Table name",type:"string",placeholder:"markdown_pages"}];function qp(e){const t={type:e,product_name:"",version:"latest",max_size:1048576};return e==="code"&&delete t.version,t.database_config={type:"sqlite",params:{db_path:""}},t}function k7(e,t){return t.reduce((n,r)=>n?.[r],e)}function sI(e,t,n){if(t.length===0)return n;const[r,...i]=t,a=Array.isArray(e)?[...e??[]]:{...e??{}};return i.length===0?n===void 0||n===""||Array.isArray(n)&&n.length===0?delete a[r]:a[r]=n:a[r]=sI(a[r],i,n),a}const $o="w-full rounded-md border border-edge bg-page px-3 py-1.5 text-sm outline-none focus:border-accent placeholder:text-ink-muted/60";function vu({field:e,value:t,onChange:n}){const[r,i]=O.useState(null);return e.type==="boolean"?T.jsxs("label",{className:"flex items-center gap-2 py-1.5 text-sm text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:!!t,onChange:a=>n(a.target.checked||void 0),className:"h-4 w-4 accent-(--accent)"}),e.label,e.help&&T.jsxs("span",{className:"text-xs text-ink-muted",children:["— ",e.help]})]}):T.jsxs("label",{className:"block",children:[T.jsxs("span",{className:"mb-1 flex items-baseline gap-2 text-xs font-medium text-ink-secondary",children:[e.label,e.required&&T.jsx("span",{className:"text-critical",children:"*"}),e.secret&&T.jsxs("span",{className:"text-ink-muted",children:["use a ",T.jsx("code",{children:"${ENV_VAR}"})," placeholder"]})]}),e.type==="select"?T.jsxs("select",{value:t??"",onChange:a=>n(a.target.value||void 0),className:$o,children:[T.jsx("option",{value:"",children:"—"}),e.options.map(a=>T.jsx("option",{value:a,children:a},a))]}):e.type==="string_list"?T.jsx("input",{value:r??(Array.isArray(t)?t.join(", "):""),placeholder:e.placeholder,onChange:a=>i(a.target.value),onBlur:()=>{r!==null&&(n(r.split(",").map(a=>a.trim()).filter(Boolean)),i(null))},className:$o}):T.jsx("input",{type:"text",inputMode:e.type==="number"?"numeric":void 0,value:t??"",placeholder:e.placeholder,onChange:a=>{const l=a.target.value;e.type==="number"?n(l===""?void 0:Number(l)):n(l)},className:$o}),e.help&&T.jsx("span",{className:"mt-1 block text-xs text-ink-muted",children:e.help})]})}function yx({fields:e,doc:t,basePath:n,onSet:r}){return T.jsx("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:e.map(i=>T.jsx("div",{className:i.type==="boolean"?"sm:col-span-2":"",children:T.jsx(vu,{field:i,value:k7(t,[...n,i.key]),onChange:a=>r([...n,i.key],a)})},i.key))})}function Wp({title:e,subtitle:t,action:n,children:r}){return T.jsxs("section",{className:"rounded-lg border border-edge bg-surface p-4",children:[T.jsxs("div",{className:"mb-3 flex items-center gap-2",children:[T.jsx("h3",{className:"text-sm font-semibold",children:e}),t&&T.jsx("span",{className:"text-xs text-ink-muted",children:t}),T.jsx("div",{className:"ml-auto",children:n})]}),r]})}function uI(e){const t=io(),n=O.useMemo(()=>{if(e.mode==="edit"&&e.config)try{const A=zV(e.config.content);return A&&typeof A=="object"?{doc:A,parseError:null}:{doc:null,parseError:"config is not a YAML mapping"}}catch(A){return{doc:null,parseError:A instanceof Error?A.message:String(A)}}return{doc:{name:"",schedule:"",sources:[qp("website")]},parseError:null}},[e.mode,e.config]),[r,i]=O.useState(n.doc),[a,l]=O.useState(""),[s,c]=O.useState(null),f=Wh({mutationFn:async()=>{const A=O7(r,{lineWidth:120,noRefs:!0}),E=await Ct.validateConfig(A);if(!E.valid)throw new fh(400,E.error??"invalid config");if(e.mode==="create"){const k=a.trim()||`${(r.name||"config").toString().replace(/[^A-Za-z0-9._-]+/g,"-")}.yaml`;return Ct.createConfig(k,A)}return Ct.updateConfig(e.config.id,A,e.config.content_hash)},onSuccess:A=>{c(null),t.invalidateQueries({queryKey:["configs"]}),e.onSaved?.(A)},onError:A=>c(A.message)});if(n.parseError||!r)return T.jsxs("p",{className:"rounded-md border border-warning/50 bg-surface px-3 py-2 text-sm text-ink-secondary",children:["⚠ This config can't be edited as a form (",n.parseError,") — use the YAML view."]});const d=(A,E)=>i(k=>sI(k,A,E)),p=Array.isArray(r.sources)?r.sources:[],g=[];p.forEach((A,E)=>{const k=Fp[A?.type];for(const M of[...HM,...k?.fields??[]]){if(!M.required||A?.type==="code"&&M.key==="version")continue;const D=A?.[M.key];(D===void 0||D==="")&&g.push(`source ${E+1}: ${M.label}`)}});const v=r.embedding!==void 0,y=r.markdown_store!==void 0,x=f.error instanceof fh&&f.error.status===409,w=r.schedule??"";return T.jsxs("div",{className:"space-y-4",children:[x&&T.jsx("div",{className:"rounded-md border border-warning/50 bg-surface px-3 py-2 text-sm text-ink-secondary",children:"⚠ The file changed on disk since you loaded it. Reload the page and reapply your edits."}),s&&!x&&T.jsx("div",{className:"rounded-md border border-critical/50 bg-surface px-3 py-2 text-sm text-critical",children:s}),T.jsx(Wp,{title:"General",children:T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[e.mode==="create"&&T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Filename"}),T.jsx("input",{value:a,onChange:A=>l(A.target.value),placeholder:`${(r.name||"my-config").toString().replace(/[^A-Za-z0-9._-]+/g,"-")}.yaml`,className:`${$o} font-mono`})]}),T.jsx(vu,{field:{key:"name",label:"Display name",type:"string",placeholder:"product-docs"},value:r.name,onChange:A=>d(["name"],A)}),T.jsxs("div",{children:[T.jsx(vu,{field:{key:"schedule",label:"Schedule (cron)",type:"string",placeholder:"0 2 * * *",help:"Leave empty for manual runs only"},value:w,onChange:A=>d(["schedule"],A)}),w&&T.jsx("p",{className:"mt-1 text-xs text-accent",children:gO(w)})]})]})}),p.map((A,E)=>{const k=Fp[A?.type]??Fp.website,M=A?.database_config?.type??"sqlite",D=vx[M]??vx.sqlite;return T.jsx(Wp,{title:`Source ${E+1}`,subtitle:A?.product_name||void 0,action:T.jsx("button",{onClick:()=>d(["sources"],p.filter((P,N)=>N!==E)),className:"text-xs text-ink-muted transition hover:text-critical",children:"Remove"}),children:T.jsxs("div",{className:"space-y-4",children:[T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Type"}),T.jsx("select",{value:A?.type??"website",onChange:P=>{const N={...qp(P.target.value),product_name:A?.product_name??"",...A?.version!==void 0&&P.target.value!=="code"&&{version:A.version},...A?.max_size!==void 0&&{max_size:A.max_size},database_config:A?.database_config??qp(P.target.value).database_config};d(["sources",E],N)},className:$o,children:Object.entries(Fp).map(([P,N])=>T.jsx("option",{value:P,children:N.label},P))})]}),HM.map(P=>T.jsx(vu,{field:A?.type==="code"&&P.key==="version"?{...P,required:!1,help:"Defaults to the branch name"}:P,value:A?.[P.key],onChange:N=>d(["sources",E,P.key],N)},P.key))]}),T.jsxs("div",{children:[T.jsxs("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-ink-muted",children:[k.label," options"]}),T.jsx(yx,{fields:k.fields,doc:r,basePath:["sources",E],onSet:d})]}),T.jsxs("div",{children:[T.jsx("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-ink-muted",children:"Vector database"}),T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Type"}),T.jsx("select",{value:M,onChange:P=>d(["sources",E,"database_config"],{type:P.target.value,params:{}}),className:$o,children:Object.entries(vx).map(([P,N])=>T.jsx("option",{value:P,children:N.label},P))})]}),D.fields.map(P=>T.jsx(vu,{field:P,value:A?.database_config?.params?.[P.key],onChange:N=>d(["sources",E,"database_config","params",P.key],N)},P.key))]})]})]})},E)}),T.jsx("button",{onClick:()=>d(["sources"],[...p,qp("website")]),className:"w-full rounded-lg border border-dashed border-edge py-2.5 text-sm font-medium text-ink-muted transition hover:border-accent hover:text-accent",children:"+ Add source"}),T.jsx(Wp,{title:"Embedding",subtitle:"optional — defaults to OpenAI text-embedding-3-large",action:T.jsxs("label",{className:"flex items-center gap-2 text-xs text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:v,onChange:A=>d(["embedding"],A.target.checked?{provider:"openai"}:void 0),className:"h-4 w-4 accent-(--accent)"}),"customize"]}),children:v?T.jsxs("div",{className:"space-y-3",children:[T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Provider"}),T.jsx("select",{value:r.embedding?.provider??"openai",onChange:A=>d(["embedding"],{provider:A.target.value}),className:$o,children:Object.entries(FM).map(([A,E])=>T.jsx("option",{value:A,children:E.label},A))})]}),T.jsx(vu,{field:{key:"dimension",label:"Dimension",type:"number",placeholder:"3072"},value:r.embedding?.dimension,onChange:A=>d(["embedding","dimension"],A)})]}),T.jsx(yx,{fields:FM[r.embedding?.provider??"openai"].fields,doc:r,basePath:["embedding",r.embedding?.provider??"openai"],onSet:d})]}):T.jsx("p",{className:"text-xs text-ink-muted",children:"Using defaults (OPENAI_API_KEY env var, text-embedding-3-large, 3072 dimensions)."})}),T.jsx(Wp,{title:"Markdown store",subtitle:"optional — store crawled markdown in Postgres",action:T.jsxs("label",{className:"flex items-center gap-2 text-xs text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:y,onChange:A=>d(["markdown_store"],A.target.checked?{}:void 0),className:"h-4 w-4 accent-(--accent)"}),"enable"]}),children:y?T.jsx(yx,{fields:A7,doc:r,basePath:["markdown_store"],onSet:d}):T.jsx("p",{className:"text-xs text-ink-muted",children:"Disabled."})}),T.jsxs("div",{className:"flex items-center gap-3",children:[T.jsx("button",{onClick:()=>f.mutate(),disabled:f.isPending||p.length===0||g.length>0,className:"rounded-md bg-accent px-4 py-2 text-sm font-medium text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:f.isPending?"Saving…":e.mode==="create"?"Create config":"Save changes"}),e.onCancel&&T.jsx("button",{onClick:e.onCancel,className:"text-sm text-ink-muted hover:text-ink-secondary",children:"Cancel"}),g.length>0&&T.jsxs("span",{className:"text-xs text-warning",children:["missing: ",g.slice(0,3).join(", "),g.length>3?"…":""]}),p.length===0&&T.jsx("span",{className:"text-xs text-warning",children:"add at least one source"}),e.mode==="edit"&&T.jsx("span",{className:"ml-auto text-xs text-ink-muted",children:"Saving rewrites the YAML file (comments are not preserved)."})]})]})}function cI({config:e,variant:t="subtle"}){const n=io(),[r,i]=O.useState(!1),[a,l]=O.useState(new Set),[s,c]=O.useState(""),f=Wh({mutationFn:k=>Ct.triggerRun(e.id,k,e.content_hash),onSuccess:()=>{i(!1),l(new Set),c("")},onSettled:()=>n.invalidateQueries({queryKey:["configs"]})}),d=O.useMemo(()=>e.source_summary.map((k,M)=>({...k,index:M})),[e.source_summary]),p=O.useMemo(()=>{const k=s.trim().toLowerCase();return k?d.filter(M=>M.product_name.toLowerCase().includes(k)||M.type.toLowerCase().includes(k)):d},[d,s]),g=e.busy||!!e.parse_error||f.isPending,v=k=>{k.preventDefault(),k.stopPropagation()},y=k=>{l(M=>{const D=new Set(M);return D.has(k)?D.delete(k):D.add(k),D})},x=p.length>0&&p.every(k=>a.has(k.index)),w=()=>{l(k=>{const M=new Set(k);return x?p.forEach(D=>M.delete(D.index)):p.forEach(D=>M.add(D.index)),M})},A=t==="primary"?"bg-accent text-white transition hover:opacity-90 px-3.5 py-1.5 text-sm font-medium":"border border-edge bg-surface text-ink-secondary transition hover:border-accent hover:text-accent px-3 py-1 text-xs font-medium",E=t==="primary"?"bg-accent text-white transition hover:opacity-90 border-l border-white/25 px-2 py-1.5 text-sm":"border border-l-0 border-edge bg-surface text-ink-secondary transition hover:border-accent hover:text-accent px-1.5 py-1 text-xs";return T.jsxs("div",{className:"relative inline-flex items-center gap-2",onClick:v,children:[T.jsxs("span",{className:"inline-flex",children:[T.jsx("button",{onClick:k=>{v(k),f.mutate(void 0)},disabled:g,className:`${d.length>1?"rounded-l-md":"rounded-md"} ${A} disabled:cursor-not-allowed disabled:opacity-40`,children:e.busy?"Running…":"Run now"}),d.length>1&&T.jsx("button",{onClick:k=>{v(k),i(M=>!M)},disabled:g,"aria-label":"Choose sources to run",title:"Choose sources to run",className:`rounded-r-md ${E} disabled:cursor-not-allowed disabled:opacity-40`,children:"▾"})]}),f.error&&T.jsx("span",{className:"text-xs text-critical",children:f.error.message}),r&&T.jsxs(T.Fragment,{children:[T.jsx("div",{className:"fixed inset-0 z-10",onClick:k=>{v(k),i(!1)}}),T.jsxs("div",{className:"absolute right-0 top-full z-20 mt-1.5 w-80 rounded-md border border-edge bg-surface shadow-lg",children:[T.jsxs("div",{className:"flex items-center justify-between border-b border-edge px-3 py-2",children:[T.jsxs("span",{className:"text-xs font-semibold uppercase tracking-wide text-ink-muted",children:["Run sources",a.size>0&&` (${a.size}/${d.length})`]}),T.jsxs("button",{onClick:k=>{v(k),w()},className:"text-xs text-accent hover:underline",children:[x?"Clear":"Select all",s.trim()&&" shown"]})]}),d.length>8&&T.jsx("div",{className:"border-b border-edge px-3 py-2",children:T.jsx("input",{type:"text",value:s,onChange:k=>c(k.target.value),onClick:k=>k.stopPropagation(),placeholder:"Filter sources…",className:"w-full rounded-md border border-edge bg-page px-2 py-1 text-xs text-ink-secondary placeholder:text-ink-muted focus:border-accent focus:outline-none"})}),T.jsxs("ul",{className:"max-h-64 overflow-y-auto py-1",children:[p.map(k=>T.jsx("li",{children:T.jsxs("label",{className:"flex cursor-pointer items-center gap-2 px-3 py-1.5 text-sm text-ink-secondary hover:bg-edge/20",onClick:M=>M.stopPropagation(),children:[T.jsx("input",{type:"checkbox",checked:a.has(k.index),onChange:()=>y(k.index),className:"accent-accent"}),T.jsx("span",{className:"truncate",children:k.product_name}),T.jsxs("span",{className:"ml-auto shrink-0 text-xs text-ink-muted",children:[k.type,k.version?` · ${k.version}`:""]})]})},k.index)),p.length===0&&T.jsxs("li",{className:"px-3 py-2 text-xs text-ink-muted",children:["No sources match “",s,"”."]})]}),T.jsx("div",{className:"border-t border-edge px-3 py-2",children:T.jsxs("button",{onClick:k=>{v(k),f.mutate([...a])},disabled:a.size===0||g,className:"w-full rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:["Run ",a.size>0?`${a.size} selected`:"selected"," source",a.size===1?"":"s"]})})]})]})]})}const C7={website:"🌐",github:"🐙",zendesk:"🎫",local_directory:"📁",code:"⌨️",s3:"🪣"};function fI({sources:e,max:t}){const[n,r]=O.useState(!1);if(e.length===0)return T.jsx("span",{className:"text-xs text-ink-muted",children:"no sources"});const i=n||t===void 0?e.length:t,a=e.slice(0,i),l=e.length-a.length;return T.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[a.map((s,c)=>T.jsxs("span",{title:`${s.type}${s.version?` · ${s.version}`:""}`,className:"inline-flex items-center gap-1 rounded-full border border-edge bg-page px-2 py-0.5 text-xs text-ink-secondary",children:[T.jsx("span",{"aria-hidden":!0,className:"text-[10px]",children:C7[s.type]??"•"}),s.product_name,T.jsx("span",{className:"text-ink-muted",children:s.type})]},`${s.product_name}-${s.type}-${c}`)),l>0&&T.jsxs("button",{onClick:s=>{s.preventDefault(),r(!0)},className:"rounded-full border border-edge px-2 py-0.5 text-xs text-ink-muted transition hover:border-accent hover:text-accent",children:["+",l," more"]})]})}function E7(){const e=Mv(),{data:t,isLoading:n,error:r}=Wi({queryKey:["configs"],queryFn:Ct.configs}),{data:i}=Wi({queryKey:["health"],queryFn:Ct.health}),[a,l]=O.useState(!1),s=t?.filter(c=>c.last_run?.status==="running"||c.last_run?.status==="queued")??[];return n?T.jsx("p",{className:"text-ink-muted",children:"Loading…"}):r?T.jsxs("p",{className:"text-critical",children:["Failed to load configs: ",String(r.message)]}):T.jsxs("div",{className:"space-y-6",children:[s.length>0&&T.jsx("div",{className:"rounded-lg border border-accent/30 bg-surface px-4 py-3",children:T.jsxs("p",{className:"text-sm text-ink-secondary",children:[T.jsxs("span",{className:"font-medium text-accent",children:["● ",s.length," config",s.length>1?"s":""," syncing now"]})," — ",s.map((c,f)=>T.jsxs("span",{children:[f>0&&", ",T.jsx(yi,{to:`/runs/${c.last_run.id}`,className:"text-accent hover:underline",children:c.name})]},c.id))]})}),T.jsxs("div",{className:"flex items-center justify-between",children:[T.jsx("h1",{className:"text-xl font-semibold tracking-tight",children:"Configurations"}),i?.mode==="rw"&&T.jsx("button",{onClick:()=>l(!0),className:"rounded-md bg-accent px-3.5 py-1.5 text-sm font-medium text-white transition hover:opacity-90",children:"New config"})]}),a&&T.jsxs("div",{className:"rounded-lg border border-edge bg-page p-4",children:[T.jsx("h2",{className:"mb-4 text-sm font-semibold",children:"Create a new config"}),T.jsx(uI,{mode:"create",onSaved:c=>{l(!1),e(`/configs/${c.id}`)},onCancel:()=>l(!1)})]}),t&&t.length===0&&T.jsxs("p",{className:"rounded-lg border border-edge bg-surface px-4 py-8 text-center text-sm text-ink-muted",children:["No configs loaded. Pass config files to ",T.jsx("code",{className:"text-ink-secondary",children:"doc2vec controller"}),i?.mode==="rw"&&" or create one with the button above","."]}),T.jsx("div",{className:"grid gap-4",children:t?.map(c=>T.jsx(yi,{to:`/configs/${c.id}`,className:"block rounded-lg border border-edge bg-surface p-4 transition hover:border-accent/50",children:T.jsxs("div",{className:"flex flex-col gap-3 md:grid md:grid-cols-[minmax(9rem,1fr)_12rem_15rem_8rem_auto] md:items-start md:gap-x-6",children:[T.jsxs("div",{className:"min-w-0",children:[T.jsxs("div",{className:"flex items-center gap-2",children:[T.jsx("h2",{className:"truncate font-semibold",children:c.name}),c.parse_error&&T.jsx("span",{className:"shrink-0 rounded-full border border-critical/40 px-2 py-0.5 text-xs text-critical",title:c.parse_error,children:"invalid"})]}),T.jsx("div",{className:"mt-1.5",children:T.jsx(fI,{sources:c.source_summary,max:4})})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Schedule"}),T.jsx("p",{className:"mt-0.5 text-ink-secondary",title:c.schedule??void 0,children:gO(c.schedule)})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Last run"}),T.jsx("div",{className:"mt-0.5 flex flex-wrap items-center gap-2",children:c.last_run?T.jsxs(T.Fragment,{children:[T.jsx(_v,{status:c.last_run.status}),T.jsxs("span",{className:"text-xs text-ink-muted",title:Fa(c.last_run.queued_at),children:[H1(c.last_run.finished_at??c.last_run.queued_at),c.last_run.finished_at&&c.last_run.started_at&&` · ${mO(c.last_run.started_at,c.last_run.finished_at)}`]})]}):T.jsx("span",{className:"text-xs text-ink-muted",children:"never"})})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Next run"}),T.jsx("p",{className:"mt-0.5 text-ink-secondary",title:c.next_run?Fa(c.next_run):void 0,children:c.next_run?H1(c.next_run):"—"})]}),T.jsx("div",{className:"md:self-center",children:T.jsx(cI,{config:c})})]})},c.id))})]})}function aS(){return aS=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},aS.apply(null,arguments)}function T7(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}let lS=[],hI=[];(()=>{let e="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(t=>t?parseInt(t,36):1);for(let t=0,n=0;t<e.length;t++)(t%2?hI:lS).push(n=n+e[t])})();function M7(e){if(e<768)return!1;for(let t=0,n=lS.length;;){let r=t+n>>1;if(e<lS[r])n=r;else if(e>=hI[r])t=r+1;else return!0;if(t==n)return!1}}function qM(e){return e>=127462&&e<=127487}const WM=8205;function P7(e,t,n=!0,r=!0){return(n?dI:D7)(e,t,r)}function dI(e,t,n){if(t==e.length)return t;t&&pI(e.charCodeAt(t))&&mI(e.charCodeAt(t-1))&&t--;let r=bx(e,t);for(t+=VM(r);t<e.length;){let i=bx(e,t);if(r==WM||i==WM||n&&M7(i))t+=VM(i),r=i;else if(qM(i)){let a=0,l=t-2;for(;l>=0&&qM(bx(e,l));)a++,l-=2;if(a%2==0)break;t+=2}else break}return t}function D7(e,t,n){for(;t>1;){let r=dI(e,t-2,n);if(r<t)return r;t--}return 0}function bx(e,t){let n=e.charCodeAt(t);if(!mI(n)||t+1==e.length)return n;let r=e.charCodeAt(t+1);return pI(r)?(n-55296<<10)+(r-56320)+65536:n}function pI(e){return e>=56320&&e<57344}function mI(e){return e>=55296&&e<56320}function VM(e){return e<65536?1:2}let Je=class gI{lineAt(t){if(t<0||t>this.length)throw new RangeError(`Invalid position ${t} in document of length ${this.length}`);return this.lineInner(t,!1,1,0)}line(t){if(t<1||t>this.lines)throw new RangeError(`Invalid line number ${t} in ${this.lines}-line document`);return this.lineInner(t,!0,1,0)}replace(t,n,r){[t,n]=Ku(this,t,n);let i=[];return this.decompose(0,t,i,2),r.length&&r.decompose(0,r.length,i,3),this.decompose(n,this.length,i,1),Hi.from(i,this.length-(n-t)+r.length)}append(t){return this.replace(this.length,this.length,t)}slice(t,n=this.length){[t,n]=Ku(this,t,n);let r=[];return this.decompose(t,n,r,0),Hi.from(r,n-t)}eq(t){if(t==this)return!0;if(t.length!=this.length||t.lines!=this.lines)return!1;let n=this.scanIdentical(t,1),r=this.length-this.scanIdentical(t,-1),i=new Gf(this),a=new Gf(t);for(let l=n,s=n;;){if(i.next(l),a.next(l),l=0,i.lineBreak!=a.lineBreak||i.done!=a.done||i.value!=a.value)return!1;if(s+=i.value.length,i.done||s>=r)return!0}}iter(t=1){return new Gf(this,t)}iterRange(t,n=this.length){return new vI(this,t,n)}iterLines(t,n){let r;if(t==null)r=this.iter();else{n==null&&(n=this.lines+1);let i=this.line(t).from;r=this.iterRange(i,Math.max(i,n==this.lines+1?this.length:n<=1?0:this.line(n-1).to))}return new yI(r)}toString(){return this.sliceString(0)}toJSON(){let t=[];return this.flatten(t),t}constructor(){}static of(t){if(t.length==0)throw new RangeError("A document must have at least one line");return t.length==1&&!t[0]?gI.empty:t.length<=32?new It(t):Hi.from(It.split(t,[]))}};class It extends Je{constructor(t,n=_7(t)){super(),this.text=t,this.length=n}get lines(){return this.text.length}get children(){return null}lineInner(t,n,r,i){for(let a=0;;a++){let l=this.text[a],s=i+l.length;if((n?r:s)>=t)return new N7(i,s,r,l);i=s+1,r++}}decompose(t,n,r,i){let a=t<=0&&n>=this.length?this:new It(KM(this.text,t,n),Math.min(n,this.length)-Math.max(0,t));if(i&1){let l=r.pop(),s=Im(a.text,l.text.slice(),0,a.length);if(s.length<=32)r.push(new It(s,l.length+a.length));else{let c=s.length>>1;r.push(new It(s.slice(0,c)),new It(s.slice(c)))}}else r.push(a)}replace(t,n,r){if(!(r instanceof It))return super.replace(t,n,r);[t,n]=Ku(this,t,n);let i=Im(this.text,Im(r.text,KM(this.text,0,t)),n),a=this.length+r.length-(n-t);return i.length<=32?new It(i,a):Hi.from(It.split(i,[]),a)}sliceString(t,n=this.length,r=`
|
|
55
|
+
`)}return r}var w7={...AO,schema:YW,skipInvalid:!1,noRefs:!1,flowLevel:-1,transform:()=>{}};function O7(e,t={}){const n={...w7,...t},r=HV(e,n.schema,{noRefs:n.noRefs,skipInvalid:n.skipInvalid});return n.flowLevel>=0&&qV(r,(i,a)=>{if(!(a.depth<n.flowLevel))return i.style.flow=!0,tI}),n.transform(r),S7(r,{...W1(n,Object.keys(AO)),schema:n.schema})}const HM=[{key:"product_name",label:"Product name",type:"string",required:!0,placeholder:"istio",help:"Stored in chunk metadata; used to identify the source"},{key:"version",label:"Version",type:"string",required:!0,placeholder:"latest",help:"Required for all types except code (defaults to branch)"},{key:"max_size",label:"Max page size (bytes)",type:"number",placeholder:"1048576",help:"Pages/files larger than this are skipped"}],Fp={website:{label:"Website",fields:[{key:"url",label:"Base URL",type:"string",required:!0,placeholder:"https://docs.example.com/"},{key:"sitemap_url",label:"Sitemap URL",type:"string",placeholder:"https://docs.example.com/sitemap.xml",help:"Discovers pages not linked in navigation; lastmod dates enable cheap change detection"},{key:"markdown_store",label:"Store markdown in Postgres",type:"boolean",help:"Requires the top-level markdown store to be configured"}]},github:{label:"GitHub issues",fields:[{key:"repo",label:"Repository",type:"string",required:!0,placeholder:"owner/repo"},{key:"start_date",label:"Start date",type:"string",placeholder:"2025-01-01",help:"Only issues updated after this date (first run)"}]},local_directory:{label:"Local directory",fields:[{key:"path",label:"Directory path",type:"string",required:!0,placeholder:"/data/docs"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".md, .txt, .pdf"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list",placeholder:".log, .tmp"},{key:"recursive",label:"Recurse into subdirectories",type:"boolean"},{key:"encoding",label:"File encoding",type:"string",placeholder:"utf8"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string",placeholder:"https://mydomain.com",help:"Rewrites file:// URLs in chunk metadata"}]},zendesk:{label:"Zendesk",fields:[{key:"zendesk_subdomain",label:"Subdomain",type:"string",required:!0,placeholder:"mycompany",help:"mycompany.zendesk.com"},{key:"email",label:"User email",type:"string",required:!0,placeholder:"agent@company.com"},{key:"api_token",label:"API token",type:"string",required:!0,secret:!0,placeholder:"${ZENDESK_API_TOKEN}"},{key:"fetch_tickets",label:"Fetch tickets",type:"boolean"},{key:"fetch_articles",label:"Fetch help center articles",type:"boolean"},{key:"start_date",label:"Start date",type:"string",placeholder:"2025-01-01"},{key:"ticket_status",label:"Ticket statuses",type:"string_list",placeholder:"new, open, pending, hold, solved"},{key:"ticket_priority",label:"Ticket priorities",type:"string_list",placeholder:"urgent, high"},{key:"excluded_organizations",label:"Excluded organizations",type:"string_list"},{key:"include_internal_comments",label:"Include internal comments",type:"boolean"}]},code:{label:"Code",fields:[{key:"source",label:"Code source",type:"select",required:!0,options:["github","local_directory"]},{key:"repo",label:"Repository",type:"string",placeholder:"owner/repo",help:"When source is github"},{key:"branch",label:"Branch",type:"string",placeholder:"main",help:"When source is github"},{key:"path",label:"Directory path",type:"string",placeholder:"/data/src",help:"When source is local_directory"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".ts, .go, .py"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list"},{key:"exclude_paths",label:"Exclude paths",type:"string_list",placeholder:"vendor/**, **/*_test.go",help:"Glob patterns relative to the source root"},{key:"recursive",label:"Recurse into subdirectories",type:"boolean"},{key:"chunk_size",label:"Chunk size (tokens)",type:"number",placeholder:"1500"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string"}]},s3:{label:"S3 bucket",fields:[{key:"bucket",label:"Bucket",type:"string",required:!0,placeholder:"my-docs-bucket"},{key:"prefix",label:"Key prefix",type:"string",placeholder:"docs/"},{key:"region",label:"Region",type:"string",placeholder:"us-east-1"},{key:"endpoint",label:"Custom endpoint",type:"string",placeholder:"https://minio.local:9000",help:"For S3-compatible services (MinIO, LocalStack)"},{key:"include_extensions",label:"Include extensions",type:"string_list",placeholder:".md, .txt, .pdf"},{key:"exclude_extensions",label:"Exclude extensions",type:"string_list"},{key:"encoding",label:"Text encoding",type:"string",placeholder:"utf8"},{key:"url_rewrite_prefix",label:"URL rewrite prefix",type:"string",help:"Rewrites s3:// URLs in chunk metadata"}]}},vx={sqlite:{label:"SQLite (sqlite-vec)",fields:[{key:"db_path",label:"Database file",type:"string",placeholder:"./my-product.db"}]},qdrant:{label:"Qdrant",fields:[{key:"qdrant_url",label:"Qdrant URL",type:"string",placeholder:"http://localhost"},{key:"qdrant_port",label:"Port",type:"number",placeholder:"6333"},{key:"collection_name",label:"Collection",type:"string",placeholder:"my-product"},{key:"keyword_index",label:"Keyword index (BM25)",type:"boolean",help:"Stores a BM25 keyword vector per chunk so the MCP server can run hybrid search (HYBRID_SEARCH=true). Needs Qdrant 1.18 or later; existing chunks are backfilled on the next sync"}]}},FM={openai:{label:"OpenAI (or compatible)",fields:[{key:"api_key",label:"API key",type:"string",secret:!0,placeholder:"${OPENAI_API_KEY}",help:"Falls back to the OPENAI_API_KEY env var"},{key:"model",label:"Model",type:"string",placeholder:"text-embedding-3-large"},{key:"base_url",label:"Base URL",type:"string",placeholder:"http://localhost:11434/v1",help:"For Ollama or other OpenAI-compatible endpoints"}]},azure:{label:"Azure OpenAI",fields:[{key:"api_key",label:"API key",type:"string",secret:!0,placeholder:"${AZURE_OPENAI_KEY}"},{key:"endpoint",label:"Endpoint",type:"string",placeholder:"https://myresource.openai.azure.com"},{key:"deployment_name",label:"Deployment name",type:"string",placeholder:"text-embedding-3-large"},{key:"api_version",label:"API version",type:"string",placeholder:"2024-10-21"}]}},A7=[{key:"connection_string",label:"Connection string",type:"string",secret:!0,placeholder:"postgres://user:${PG_PASSWORD}@host:5432/db",help:"Either this, or the discrete fields below"},{key:"host",label:"Host",type:"string"},{key:"port",label:"Port",type:"number",placeholder:"5432"},{key:"database",label:"Database",type:"string"},{key:"user",label:"User",type:"string"},{key:"password",label:"Password",type:"string",secret:!0,placeholder:"${PG_PASSWORD}"},{key:"table_name",label:"Table name",type:"string",placeholder:"markdown_pages"}];function qp(e){const t={type:e,product_name:"",version:"latest",max_size:1048576};return e==="code"&&delete t.version,t.database_config={type:"sqlite",params:{db_path:""}},t}function k7(e,t){return t.reduce((n,r)=>n?.[r],e)}function sI(e,t,n){if(t.length===0)return n;const[r,...i]=t,a=Array.isArray(e)?[...e??[]]:{...e??{}};return i.length===0?n===void 0||n===""||Array.isArray(n)&&n.length===0?delete a[r]:a[r]=n:a[r]=sI(a[r],i,n),a}const $o="w-full rounded-md border border-edge bg-page px-3 py-1.5 text-sm outline-none focus:border-accent placeholder:text-ink-muted/60";function vu({field:e,value:t,onChange:n}){const[r,i]=O.useState(null);return e.type==="boolean"?T.jsxs("label",{className:"flex items-center gap-2 py-1.5 text-sm text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:!!t,onChange:a=>n(a.target.checked||void 0),className:"h-4 w-4 accent-(--accent)"}),e.label,e.help&&T.jsxs("span",{className:"text-xs text-ink-muted",children:["— ",e.help]})]}):T.jsxs("label",{className:"block",children:[T.jsxs("span",{className:"mb-1 flex items-baseline gap-2 text-xs font-medium text-ink-secondary",children:[e.label,e.required&&T.jsx("span",{className:"text-critical",children:"*"}),e.secret&&T.jsxs("span",{className:"text-ink-muted",children:["use a ",T.jsx("code",{children:"${ENV_VAR}"})," placeholder"]})]}),e.type==="select"?T.jsxs("select",{value:t??"",onChange:a=>n(a.target.value||void 0),className:$o,children:[T.jsx("option",{value:"",children:"—"}),e.options.map(a=>T.jsx("option",{value:a,children:a},a))]}):e.type==="string_list"?T.jsx("input",{value:r??(Array.isArray(t)?t.join(", "):""),placeholder:e.placeholder,onChange:a=>i(a.target.value),onBlur:()=>{r!==null&&(n(r.split(",").map(a=>a.trim()).filter(Boolean)),i(null))},className:$o}):T.jsx("input",{type:"text",inputMode:e.type==="number"?"numeric":void 0,value:t??"",placeholder:e.placeholder,onChange:a=>{const l=a.target.value;e.type==="number"?n(l===""?void 0:Number(l)):n(l)},className:$o}),e.help&&T.jsx("span",{className:"mt-1 block text-xs text-ink-muted",children:e.help})]})}function yx({fields:e,doc:t,basePath:n,onSet:r}){return T.jsx("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:e.map(i=>T.jsx("div",{className:i.type==="boolean"?"sm:col-span-2":"",children:T.jsx(vu,{field:i,value:k7(t,[...n,i.key]),onChange:a=>r([...n,i.key],a)})},i.key))})}function Wp({title:e,subtitle:t,action:n,children:r}){return T.jsxs("section",{className:"rounded-lg border border-edge bg-surface p-4",children:[T.jsxs("div",{className:"mb-3 flex items-center gap-2",children:[T.jsx("h3",{className:"text-sm font-semibold",children:e}),t&&T.jsx("span",{className:"text-xs text-ink-muted",children:t}),T.jsx("div",{className:"ml-auto",children:n})]}),r]})}function uI(e){const t=io(),n=O.useMemo(()=>{if(e.mode==="edit"&&e.config)try{const A=zV(e.config.content);return A&&typeof A=="object"?{doc:A,parseError:null}:{doc:null,parseError:"config is not a YAML mapping"}}catch(A){return{doc:null,parseError:A instanceof Error?A.message:String(A)}}return{doc:{name:"",schedule:"",sources:[qp("website")]},parseError:null}},[e.mode,e.config]),[r,i]=O.useState(n.doc),[a,l]=O.useState(""),[s,c]=O.useState(null),f=Wh({mutationFn:async()=>{const A=O7(r,{lineWidth:120,noRefs:!0}),E=await Ct.validateConfig(A);if(!E.valid)throw new fh(400,E.error??"invalid config");if(e.mode==="create"){const k=a.trim()||`${(r.name||"config").toString().replace(/[^A-Za-z0-9._-]+/g,"-")}.yaml`;return Ct.createConfig(k,A)}return Ct.updateConfig(e.config.id,A,e.config.content_hash)},onSuccess:A=>{c(null),t.invalidateQueries({queryKey:["configs"]}),e.onSaved?.(A)},onError:A=>c(A.message)});if(n.parseError||!r)return T.jsxs("p",{className:"rounded-md border border-warning/50 bg-surface px-3 py-2 text-sm text-ink-secondary",children:["⚠ This config can't be edited as a form (",n.parseError,") — use the YAML view."]});const d=(A,E)=>i(k=>sI(k,A,E)),p=Array.isArray(r.sources)?r.sources:[],g=[];p.forEach((A,E)=>{const k=Fp[A?.type];for(const M of[...HM,...k?.fields??[]]){if(!M.required||A?.type==="code"&&M.key==="version")continue;const D=A?.[M.key];(D===void 0||D==="")&&g.push(`source ${E+1}: ${M.label}`)}});const v=r.embedding!==void 0,y=r.markdown_store!==void 0,x=f.error instanceof fh&&f.error.status===409,w=r.schedule??"";return T.jsxs("div",{className:"space-y-4",children:[x&&T.jsx("div",{className:"rounded-md border border-warning/50 bg-surface px-3 py-2 text-sm text-ink-secondary",children:"⚠ The file changed on disk since you loaded it. Reload the page and reapply your edits."}),s&&!x&&T.jsx("div",{className:"rounded-md border border-critical/50 bg-surface px-3 py-2 text-sm text-critical",children:s}),T.jsx(Wp,{title:"General",children:T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[e.mode==="create"&&T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Filename"}),T.jsx("input",{value:a,onChange:A=>l(A.target.value),placeholder:`${(r.name||"my-config").toString().replace(/[^A-Za-z0-9._-]+/g,"-")}.yaml`,className:`${$o} font-mono`})]}),T.jsx(vu,{field:{key:"name",label:"Display name",type:"string",placeholder:"product-docs"},value:r.name,onChange:A=>d(["name"],A)}),T.jsxs("div",{children:[T.jsx(vu,{field:{key:"schedule",label:"Schedule (cron)",type:"string",placeholder:"0 2 * * *",help:"Leave empty for manual runs only"},value:w,onChange:A=>d(["schedule"],A)}),w&&T.jsx("p",{className:"mt-1 text-xs text-accent",children:gO(w)})]})]})}),p.map((A,E)=>{const k=Fp[A?.type]??Fp.website,M=A?.database_config?.type??"sqlite",D=vx[M]??vx.sqlite;return T.jsx(Wp,{title:`Source ${E+1}`,subtitle:A?.product_name||void 0,action:T.jsx("button",{onClick:()=>d(["sources"],p.filter((P,N)=>N!==E)),className:"text-xs text-ink-muted transition hover:text-critical",children:"Remove"}),children:T.jsxs("div",{className:"space-y-4",children:[T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Type"}),T.jsx("select",{value:A?.type??"website",onChange:P=>{const N={...qp(P.target.value),product_name:A?.product_name??"",...A?.version!==void 0&&P.target.value!=="code"&&{version:A.version},...A?.max_size!==void 0&&{max_size:A.max_size},database_config:A?.database_config??qp(P.target.value).database_config};d(["sources",E],N)},className:$o,children:Object.entries(Fp).map(([P,N])=>T.jsx("option",{value:P,children:N.label},P))})]}),HM.map(P=>T.jsx(vu,{field:A?.type==="code"&&P.key==="version"?{...P,required:!1,help:"Defaults to the branch name"}:P,value:A?.[P.key],onChange:N=>d(["sources",E,P.key],N)},P.key))]}),T.jsxs("div",{children:[T.jsxs("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-ink-muted",children:[k.label," options"]}),T.jsx(yx,{fields:k.fields,doc:r,basePath:["sources",E],onSet:d})]}),T.jsxs("div",{children:[T.jsx("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-ink-muted",children:"Vector database"}),T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Type"}),T.jsx("select",{value:M,onChange:P=>d(["sources",E,"database_config"],{type:P.target.value,params:{}}),className:$o,children:Object.entries(vx).map(([P,N])=>T.jsx("option",{value:P,children:N.label},P))})]}),D.fields.map(P=>T.jsx(vu,{field:P,value:A?.database_config?.params?.[P.key],onChange:N=>d(["sources",E,"database_config","params",P.key],N)},P.key))]})]})]})},E)}),T.jsx("button",{onClick:()=>d(["sources"],[...p,qp("website")]),className:"w-full rounded-lg border border-dashed border-edge py-2.5 text-sm font-medium text-ink-muted transition hover:border-accent hover:text-accent",children:"+ Add source"}),T.jsx(Wp,{title:"Embedding",subtitle:"optional — defaults to OpenAI text-embedding-3-large",action:T.jsxs("label",{className:"flex items-center gap-2 text-xs text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:v,onChange:A=>d(["embedding"],A.target.checked?{provider:"openai"}:void 0),className:"h-4 w-4 accent-(--accent)"}),"customize"]}),children:v?T.jsxs("div",{className:"space-y-3",children:[T.jsxs("div",{className:"grid gap-x-4 gap-y-3 sm:grid-cols-2",children:[T.jsxs("label",{className:"block",children:[T.jsx("span",{className:"mb-1 block text-xs font-medium text-ink-secondary",children:"Provider"}),T.jsx("select",{value:r.embedding?.provider??"openai",onChange:A=>d(["embedding"],{provider:A.target.value}),className:$o,children:Object.entries(FM).map(([A,E])=>T.jsx("option",{value:A,children:E.label},A))})]}),T.jsx(vu,{field:{key:"dimension",label:"Dimension",type:"number",placeholder:"3072"},value:r.embedding?.dimension,onChange:A=>d(["embedding","dimension"],A)})]}),T.jsx(yx,{fields:FM[r.embedding?.provider??"openai"].fields,doc:r,basePath:["embedding",r.embedding?.provider??"openai"],onSet:d})]}):T.jsx("p",{className:"text-xs text-ink-muted",children:"Using defaults (OPENAI_API_KEY env var, text-embedding-3-large, 3072 dimensions)."})}),T.jsx(Wp,{title:"Markdown store",subtitle:"optional — store crawled markdown in Postgres",action:T.jsxs("label",{className:"flex items-center gap-2 text-xs text-ink-secondary",children:[T.jsx("input",{type:"checkbox",checked:y,onChange:A=>d(["markdown_store"],A.target.checked?{}:void 0),className:"h-4 w-4 accent-(--accent)"}),"enable"]}),children:y?T.jsx(yx,{fields:A7,doc:r,basePath:["markdown_store"],onSet:d}):T.jsx("p",{className:"text-xs text-ink-muted",children:"Disabled."})}),T.jsxs("div",{className:"flex items-center gap-3",children:[T.jsx("button",{onClick:()=>f.mutate(),disabled:f.isPending||p.length===0||g.length>0,className:"rounded-md bg-accent px-4 py-2 text-sm font-medium text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:f.isPending?"Saving…":e.mode==="create"?"Create config":"Save changes"}),e.onCancel&&T.jsx("button",{onClick:e.onCancel,className:"text-sm text-ink-muted hover:text-ink-secondary",children:"Cancel"}),g.length>0&&T.jsxs("span",{className:"text-xs text-warning",children:["missing: ",g.slice(0,3).join(", "),g.length>3?"…":""]}),p.length===0&&T.jsx("span",{className:"text-xs text-warning",children:"add at least one source"}),e.mode==="edit"&&T.jsx("span",{className:"ml-auto text-xs text-ink-muted",children:"Saving rewrites the YAML file (comments are not preserved)."})]})]})}function cI({config:e,variant:t="subtle"}){const n=io(),[r,i]=O.useState(!1),[a,l]=O.useState(new Set),[s,c]=O.useState(""),f=Wh({mutationFn:k=>Ct.triggerRun(e.id,k,e.content_hash),onSuccess:()=>{i(!1),l(new Set),c("")},onSettled:()=>n.invalidateQueries({queryKey:["configs"]})}),d=O.useMemo(()=>e.source_summary.map((k,M)=>({...k,index:M})),[e.source_summary]),p=O.useMemo(()=>{const k=s.trim().toLowerCase();return k?d.filter(M=>M.product_name.toLowerCase().includes(k)||M.type.toLowerCase().includes(k)):d},[d,s]),g=e.busy||!!e.parse_error||f.isPending,v=k=>{k.preventDefault(),k.stopPropagation()},y=k=>{l(M=>{const D=new Set(M);return D.has(k)?D.delete(k):D.add(k),D})},x=p.length>0&&p.every(k=>a.has(k.index)),w=()=>{l(k=>{const M=new Set(k);return x?p.forEach(D=>M.delete(D.index)):p.forEach(D=>M.add(D.index)),M})},A=t==="primary"?"bg-accent text-white transition hover:opacity-90 px-3.5 py-1.5 text-sm font-medium":"border border-edge bg-surface text-ink-secondary transition hover:border-accent hover:text-accent px-3 py-1 text-xs font-medium",E=t==="primary"?"bg-accent text-white transition hover:opacity-90 border-l border-white/25 px-2 py-1.5 text-sm":"border border-l-0 border-edge bg-surface text-ink-secondary transition hover:border-accent hover:text-accent px-1.5 py-1 text-xs";return T.jsxs("div",{className:"relative inline-flex items-center gap-2",onClick:v,children:[T.jsxs("span",{className:"inline-flex",children:[T.jsx("button",{onClick:k=>{v(k),f.mutate(void 0)},disabled:g,className:`${d.length>1?"rounded-l-md":"rounded-md"} ${A} disabled:cursor-not-allowed disabled:opacity-40`,children:e.busy?"Running…":"Run now"}),d.length>1&&T.jsx("button",{onClick:k=>{v(k),i(M=>!M)},disabled:g,"aria-label":"Choose sources to run",title:"Choose sources to run",className:`rounded-r-md ${E} disabled:cursor-not-allowed disabled:opacity-40`,children:"▾"})]}),f.error&&T.jsx("span",{className:"text-xs text-critical",children:f.error.message}),r&&T.jsxs(T.Fragment,{children:[T.jsx("div",{className:"fixed inset-0 z-10",onClick:k=>{v(k),i(!1)}}),T.jsxs("div",{className:"absolute right-0 top-full z-20 mt-1.5 w-80 rounded-md border border-edge bg-surface shadow-lg",children:[T.jsxs("div",{className:"flex items-center justify-between border-b border-edge px-3 py-2",children:[T.jsxs("span",{className:"text-xs font-semibold uppercase tracking-wide text-ink-muted",children:["Run sources",a.size>0&&` (${a.size}/${d.length})`]}),T.jsxs("button",{onClick:k=>{v(k),w()},className:"text-xs text-accent hover:underline",children:[x?"Clear":"Select all",s.trim()&&" shown"]})]}),d.length>8&&T.jsx("div",{className:"border-b border-edge px-3 py-2",children:T.jsx("input",{type:"text",value:s,onChange:k=>c(k.target.value),onClick:k=>k.stopPropagation(),placeholder:"Filter sources…",className:"w-full rounded-md border border-edge bg-page px-2 py-1 text-xs text-ink-secondary placeholder:text-ink-muted focus:border-accent focus:outline-none"})}),T.jsxs("ul",{className:"max-h-64 overflow-y-auto py-1",children:[p.map(k=>T.jsx("li",{children:T.jsxs("label",{className:"flex cursor-pointer items-center gap-2 px-3 py-1.5 text-sm text-ink-secondary hover:bg-edge/20",onClick:M=>M.stopPropagation(),children:[T.jsx("input",{type:"checkbox",checked:a.has(k.index),onChange:()=>y(k.index),className:"accent-accent"}),T.jsx("span",{className:"truncate",children:k.product_name}),T.jsxs("span",{className:"ml-auto shrink-0 text-xs text-ink-muted",children:[k.type,k.version?` · ${k.version}`:""]})]})},k.index)),p.length===0&&T.jsxs("li",{className:"px-3 py-2 text-xs text-ink-muted",children:["No sources match “",s,"”."]})]}),T.jsx("div",{className:"border-t border-edge px-3 py-2",children:T.jsxs("button",{onClick:k=>{v(k),f.mutate([...a])},disabled:a.size===0||g,className:"w-full rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:["Run ",a.size>0?`${a.size} selected`:"selected"," source",a.size===1?"":"s"]})})]})]})]})}const C7={website:"🌐",github:"🐙",zendesk:"🎫",local_directory:"📁",code:"⌨️",s3:"🪣"};function fI({sources:e,max:t}){const[n,r]=O.useState(!1);if(e.length===0)return T.jsx("span",{className:"text-xs text-ink-muted",children:"no sources"});const i=n||t===void 0?e.length:t,a=e.slice(0,i),l=e.length-a.length;return T.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[a.map((s,c)=>T.jsxs("span",{title:`${s.type}${s.version?` · ${s.version}`:""}`,className:"inline-flex items-center gap-1 rounded-full border border-edge bg-page px-2 py-0.5 text-xs text-ink-secondary",children:[T.jsx("span",{"aria-hidden":!0,className:"text-[10px]",children:C7[s.type]??"•"}),s.product_name,T.jsx("span",{className:"text-ink-muted",children:s.type})]},`${s.product_name}-${s.type}-${c}`)),l>0&&T.jsxs("button",{onClick:s=>{s.preventDefault(),r(!0)},className:"rounded-full border border-edge px-2 py-0.5 text-xs text-ink-muted transition hover:border-accent hover:text-accent",children:["+",l," more"]})]})}function E7(){const e=Mv(),{data:t,isLoading:n,error:r}=Wi({queryKey:["configs"],queryFn:Ct.configs}),{data:i}=Wi({queryKey:["health"],queryFn:Ct.health}),[a,l]=O.useState(!1),s=t?.filter(c=>c.last_run?.status==="running"||c.last_run?.status==="queued")??[];return n?T.jsx("p",{className:"text-ink-muted",children:"Loading…"}):r?T.jsxs("p",{className:"text-critical",children:["Failed to load configs: ",String(r.message)]}):T.jsxs("div",{className:"space-y-6",children:[s.length>0&&T.jsx("div",{className:"rounded-lg border border-accent/30 bg-surface px-4 py-3",children:T.jsxs("p",{className:"text-sm text-ink-secondary",children:[T.jsxs("span",{className:"font-medium text-accent",children:["● ",s.length," config",s.length>1?"s":""," syncing now"]})," — ",s.map((c,f)=>T.jsxs("span",{children:[f>0&&", ",T.jsx(yi,{to:`/runs/${c.last_run.id}`,className:"text-accent hover:underline",children:c.name})]},c.id))]})}),T.jsxs("div",{className:"flex items-center justify-between",children:[T.jsx("h1",{className:"text-xl font-semibold tracking-tight",children:"Configurations"}),i?.mode==="rw"&&T.jsx("button",{onClick:()=>l(!0),className:"rounded-md bg-accent px-3.5 py-1.5 text-sm font-medium text-white transition hover:opacity-90",children:"New config"})]}),a&&T.jsxs("div",{className:"rounded-lg border border-edge bg-page p-4",children:[T.jsx("h2",{className:"mb-4 text-sm font-semibold",children:"Create a new config"}),T.jsx(uI,{mode:"create",onSaved:c=>{l(!1),e(`/configs/${c.id}`)},onCancel:()=>l(!1)})]}),t&&t.length===0&&T.jsxs("p",{className:"rounded-lg border border-edge bg-surface px-4 py-8 text-center text-sm text-ink-muted",children:["No configs loaded. Pass config files to ",T.jsx("code",{className:"text-ink-secondary",children:"doc2vec controller"}),i?.mode==="rw"&&" or create one with the button above","."]}),T.jsx("div",{className:"grid gap-4",children:t?.map(c=>T.jsx(yi,{to:`/configs/${c.id}`,className:"block rounded-lg border border-edge bg-surface p-4 transition hover:border-accent/50",children:T.jsxs("div",{className:"flex flex-col gap-3 md:grid md:grid-cols-[minmax(9rem,1fr)_12rem_15rem_8rem_auto] md:items-start md:gap-x-6",children:[T.jsxs("div",{className:"min-w-0",children:[T.jsxs("div",{className:"flex items-center gap-2",children:[T.jsx("h2",{className:"truncate font-semibold",children:c.name}),c.parse_error&&T.jsx("span",{className:"shrink-0 rounded-full border border-critical/40 px-2 py-0.5 text-xs text-critical",title:c.parse_error,children:"invalid"})]}),T.jsx("div",{className:"mt-1.5",children:T.jsx(fI,{sources:c.source_summary,max:4})})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Schedule"}),T.jsx("p",{className:"mt-0.5 text-ink-secondary",title:c.schedule??void 0,children:gO(c.schedule)})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Last run"}),T.jsx("div",{className:"mt-0.5 flex flex-wrap items-center gap-2",children:c.last_run?T.jsxs(T.Fragment,{children:[T.jsx(_v,{status:c.last_run.status}),T.jsxs("span",{className:"text-xs text-ink-muted",title:Fa(c.last_run.queued_at),children:[H1(c.last_run.finished_at??c.last_run.queued_at),c.last_run.finished_at&&c.last_run.started_at&&` · ${mO(c.last_run.started_at,c.last_run.finished_at)}`]})]}):T.jsx("span",{className:"text-xs text-ink-muted",children:"never"})})]}),T.jsxs("div",{className:"text-sm",children:[T.jsx("p",{className:"text-xs uppercase tracking-wide text-ink-muted",children:"Next run"}),T.jsx("p",{className:"mt-0.5 text-ink-secondary",title:c.next_run?Fa(c.next_run):void 0,children:c.next_run?H1(c.next_run):"—"})]}),T.jsx("div",{className:"md:self-center",children:T.jsx(cI,{config:c})})]})},c.id))})]})}function aS(){return aS=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},aS.apply(null,arguments)}function T7(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}let lS=[],hI=[];(()=>{let e="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(t=>t?parseInt(t,36):1);for(let t=0,n=0;t<e.length;t++)(t%2?hI:lS).push(n=n+e[t])})();function M7(e){if(e<768)return!1;for(let t=0,n=lS.length;;){let r=t+n>>1;if(e<lS[r])n=r;else if(e>=hI[r])t=r+1;else return!0;if(t==n)return!1}}function qM(e){return e>=127462&&e<=127487}const WM=8205;function P7(e,t,n=!0,r=!0){return(n?dI:D7)(e,t,r)}function dI(e,t,n){if(t==e.length)return t;t&&pI(e.charCodeAt(t))&&mI(e.charCodeAt(t-1))&&t--;let r=bx(e,t);for(t+=VM(r);t<e.length;){let i=bx(e,t);if(r==WM||i==WM||n&&M7(i))t+=VM(i),r=i;else if(qM(i)){let a=0,l=t-2;for(;l>=0&&qM(bx(e,l));)a++,l-=2;if(a%2==0)break;t+=2}else break}return t}function D7(e,t,n){for(;t>1;){let r=dI(e,t-2,n);if(r<t)return r;t--}return 0}function bx(e,t){let n=e.charCodeAt(t);if(!mI(n)||t+1==e.length)return n;let r=e.charCodeAt(t+1);return pI(r)?(n-55296<<10)+(r-56320)+65536:n}function pI(e){return e>=56320&&e<57344}function mI(e){return e>=55296&&e<56320}function VM(e){return e<65536?1:2}let Je=class gI{lineAt(t){if(t<0||t>this.length)throw new RangeError(`Invalid position ${t} in document of length ${this.length}`);return this.lineInner(t,!1,1,0)}line(t){if(t<1||t>this.lines)throw new RangeError(`Invalid line number ${t} in ${this.lines}-line document`);return this.lineInner(t,!0,1,0)}replace(t,n,r){[t,n]=Ku(this,t,n);let i=[];return this.decompose(0,t,i,2),r.length&&r.decompose(0,r.length,i,3),this.decompose(n,this.length,i,1),Hi.from(i,this.length-(n-t)+r.length)}append(t){return this.replace(this.length,this.length,t)}slice(t,n=this.length){[t,n]=Ku(this,t,n);let r=[];return this.decompose(t,n,r,0),Hi.from(r,n-t)}eq(t){if(t==this)return!0;if(t.length!=this.length||t.lines!=this.lines)return!1;let n=this.scanIdentical(t,1),r=this.length-this.scanIdentical(t,-1),i=new Gf(this),a=new Gf(t);for(let l=n,s=n;;){if(i.next(l),a.next(l),l=0,i.lineBreak!=a.lineBreak||i.done!=a.done||i.value!=a.value)return!1;if(s+=i.value.length,i.done||s>=r)return!0}}iter(t=1){return new Gf(this,t)}iterRange(t,n=this.length){return new vI(this,t,n)}iterLines(t,n){let r;if(t==null)r=this.iter();else{n==null&&(n=this.lines+1);let i=this.line(t).from;r=this.iterRange(i,Math.max(i,n==this.lines+1?this.length:n<=1?0:this.line(n-1).to))}return new yI(r)}toString(){return this.sliceString(0)}toJSON(){let t=[];return this.flatten(t),t}constructor(){}static of(t){if(t.length==0)throw new RangeError("A document must have at least one line");return t.length==1&&!t[0]?gI.empty:t.length<=32?new It(t):Hi.from(It.split(t,[]))}};class It extends Je{constructor(t,n=_7(t)){super(),this.text=t,this.length=n}get lines(){return this.text.length}get children(){return null}lineInner(t,n,r,i){for(let a=0;;a++){let l=this.text[a],s=i+l.length;if((n?r:s)>=t)return new N7(i,s,r,l);i=s+1,r++}}decompose(t,n,r,i){let a=t<=0&&n>=this.length?this:new It(KM(this.text,t,n),Math.min(n,this.length)-Math.max(0,t));if(i&1){let l=r.pop(),s=Im(a.text,l.text.slice(),0,a.length);if(s.length<=32)r.push(new It(s,l.length+a.length));else{let c=s.length>>1;r.push(new It(s.slice(0,c)),new It(s.slice(c)))}}else r.push(a)}replace(t,n,r){if(!(r instanceof It))return super.replace(t,n,r);[t,n]=Ku(this,t,n);let i=Im(this.text,Im(r.text,KM(this.text,0,t)),n),a=this.length+r.length-(n-t);return i.length<=32?new It(i,a):Hi.from(It.split(i,[]),a)}sliceString(t,n=this.length,r=`
|
|
56
56
|
`){[t,n]=Ku(this,t,n);let i="";for(let a=0,l=0;a<=n&&l<this.text.length;l++){let s=this.text[l],c=a+s.length;a>t&&l&&(i+=r),t<c&&n>a&&(i+=s.slice(Math.max(0,t-a),n-a)),a=c+1}return i}flatten(t){for(let n of this.text)t.push(n)}scanIdentical(){return 0}static split(t,n){let r=[],i=-1;for(let a of t)r.push(a),i+=a.length+1,r.length==32&&(n.push(new It(r,i)),r=[],i=-1);return i>-1&&n.push(new It(r,i)),n}}class Hi extends Je{constructor(t,n){super(),this.children=t,this.length=n,this.lines=0;for(let r of t)this.lines+=r.lines}lineInner(t,n,r,i){for(let a=0;;a++){let l=this.children[a],s=i+l.length,c=r+l.lines-1;if((n?c:s)>=t)return l.lineInner(t,n,r,i);i=s+1,r=c+1}}decompose(t,n,r,i){for(let a=0,l=0;l<=n&&a<this.children.length;a++){let s=this.children[a],c=l+s.length;if(t<=c&&n>=l){let f=i&((l<=t?1:0)|(c>=n?2:0));l>=t&&c<=n&&!f?r.push(s):s.decompose(t-l,n-l,r,f)}l=c+1}}replace(t,n,r){if([t,n]=Ku(this,t,n),r.lines<this.lines)for(let i=0,a=0;i<this.children.length;i++){let l=this.children[i],s=a+l.length;if(t>=a&&n<=s){let c=l.replace(t-a,n-a,r),f=this.lines-l.lines+c.lines;if(c.lines<f>>4&&c.lines>f>>6){let d=this.children.slice();return d[i]=c,new Hi(d,this.length-(n-t)+r.length)}return super.replace(a,s,c)}a=s+1}return super.replace(t,n,r)}sliceString(t,n=this.length,r=`
|
|
57
57
|
`){[t,n]=Ku(this,t,n);let i="";for(let a=0,l=0;a<this.children.length&&l<=n;a++){let s=this.children[a],c=l+s.length;l>t&&a&&(i+=r),t<c&&n>l&&(i+=s.sliceString(t-l,n-l,r)),l=c+1}return i}flatten(t){for(let n of this.children)n.flatten(t)}scanIdentical(t,n){if(!(t instanceof Hi))return 0;let r=0,[i,a,l,s]=n>0?[0,0,this.children.length,t.children.length]:[this.children.length-1,t.children.length-1,-1,-1];for(;;i+=n,a+=n){if(i==l||a==s)return r;let c=this.children[i],f=t.children[a];if(c!=f)return r+c.scanIdentical(f,n);r+=c.length+1}}static from(t,n=t.reduce((r,i)=>r+i.length+1,-1)){let r=0;for(let v of t)r+=v.lines;if(r<32){let v=[];for(let y of t)y.flatten(v);return new It(v,n)}let i=Math.max(32,r>>5),a=i<<1,l=i>>1,s=[],c=0,f=-1,d=[];function p(v){let y;if(v.lines>a&&v instanceof Hi)for(let x of v.children)p(x);else v.lines>l&&(c>l||!c)?(g(),s.push(v)):v instanceof It&&c&&(y=d[d.length-1])instanceof It&&v.lines+y.lines<=32?(c+=v.lines,f+=v.length+1,d[d.length-1]=new It(y.text.concat(v.text),y.length+1+v.length)):(c+v.lines>i&&g(),c+=v.lines,f+=v.length+1,d.push(v))}function g(){c!=0&&(s.push(d.length==1?d[0]:Hi.from(d,f)),f=-1,c=d.length=0)}for(let v of t)p(v);return g(),s.length==1?s[0]:new Hi(s,n)}}Je.empty=new It([""],0);function _7(e){let t=-1;for(let n of e)t+=n.length+1;return t}function Im(e,t,n=0,r=1e9){for(let i=0,a=0,l=!0;a<e.length&&i<=r;a++){let s=e[a],c=i+s.length;c>=n&&(c>r&&(s=s.slice(0,r-i)),i<n&&(s=s.slice(n-i)),l?(t[t.length-1]+=s,l=!1):t.push(s)),i=c+1}return t}function KM(e,t,n){return Im(e,[""],t,n)}class Gf{constructor(t,n=1){this.dir=n,this.done=!1,this.lineBreak=!1,this.value="",this.nodes=[t],this.offsets=[n>0?1:(t instanceof It?t.text.length:t.children.length)<<1]}nextInner(t,n){for(this.done=this.lineBreak=!1;;){let r=this.nodes.length-1,i=this.nodes[r],a=this.offsets[r],l=a>>1,s=i instanceof It?i.text.length:i.children.length;if(l==(n>0?s:0)){if(r==0)return this.done=!0,this.value="",this;n>0&&this.offsets[r-1]++,this.nodes.pop(),this.offsets.pop()}else if((a&1)==(n>0?0:1)){if(this.offsets[r]+=n,t==0)return this.lineBreak=!0,this.value=`
|
|
58
58
|
`,this;t--}else if(i instanceof It){let c=i.text[l+(n<0?-1:0)];if(this.offsets[r]+=n,c.length>Math.max(0,t))return this.value=t==0?c:n>0?c.slice(t):c.slice(0,c.length-t),this;t-=c.length}else{let c=i.children[l+(n<0?-1:0)];t>c.length?(t-=c.length,this.offsets[r]+=n):(n<0&&this.offsets[r]--,this.nodes.push(c),this.offsets.push(n>0?1:(c instanceof It?c.text.length:c.children.length)<<1))}}}next(t=0){return t<0&&(this.nextInner(-t,-this.dir),t=this.value.length),this.nextInner(t,this.dir)}}class vI{constructor(t,n,r){this.value="",this.done=!1,this.cursor=new Gf(t,n>r?-1:1),this.pos=n>r?t.length:0,this.from=Math.min(n,r),this.to=Math.max(n,r)}nextInner(t,n){if(n<0?this.pos<=this.from:this.pos>=this.to)return this.value="",this.done=!0,this;t+=Math.max(0,n<0?this.pos-this.to:this.from-this.pos);let r=n<0?this.pos-this.from:this.to-this.pos;t>r&&(t=r),r-=t;let{value:i}=this.cursor.next(t);return this.pos+=(i.length+t)*n,this.value=i.length<=r?i:n<0?i.slice(i.length-r):i.slice(0,r),this.done=!this.value,this}next(t=0){return t<0?t=Math.max(t,this.from-this.pos):t>0&&(t=Math.min(t,this.to-this.pos)),this.nextInner(t,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&this.value!=""}}class yI{constructor(t){this.inner=t,this.afterBreak=!0,this.value="",this.done=!1}next(t=0){let{done:n,lineBreak:r,value:i}=this.inner.next(t);return n&&this.afterBreak?(this.value="",this.afterBreak=!1):n?(this.done=!0,this.value=""):r?this.afterBreak?this.value="":(this.afterBreak=!0,this.next()):(this.value=i,this.afterBreak=!1),this}get lineBreak(){return!1}}typeof Symbol<"u"&&(Je.prototype[Symbol.iterator]=function(){return this.iter()},Gf.prototype[Symbol.iterator]=vI.prototype[Symbol.iterator]=yI.prototype[Symbol.iterator]=function(){return this});let N7=class{constructor(t,n,r,i){this.from=t,this.to=n,this.number=r,this.text=i}get length(){return this.to-this.from}};function Ku(e,t,n){return t=Math.max(0,Math.min(e.length,t)),[t,Math.max(t,Math.min(e.length,n))]}function on(e,t,n=!0,r=!0){return P7(e,t,n,r)}function R7(e){return e>=56320&&e<57344}function j7(e){return e>=55296&&e<56320}function ar(e,t){let n=e.charCodeAt(t);if(!j7(n)||t+1==e.length)return n;let r=e.charCodeAt(t+1);return R7(r)?(n-55296<<10)+(r-56320)+65536:n}function EO(e){return e<=65535?String.fromCharCode(e):(e-=65536,String.fromCharCode((e>>10)+55296,(e&1023)+56320))}function Fi(e){return e<65536?1:2}const oS=/\r\n?|\n/;var Kn=(function(e){return e[e.Simple=0]="Simple",e[e.TrackDel=1]="TrackDel",e[e.TrackBefore=2]="TrackBefore",e[e.TrackAfter=3]="TrackAfter",e})(Kn||(Kn={}));class Zi{constructor(t){this.sections=t}get length(){let t=0;for(let n=0;n<this.sections.length;n+=2)t+=this.sections[n];return t}get newLength(){let t=0;for(let n=0;n<this.sections.length;n+=2){let r=this.sections[n+1];t+=r<0?this.sections[n]:r}return t}get empty(){return this.sections.length==0||this.sections.length==2&&this.sections[1]<0}iterGaps(t){for(let n=0,r=0,i=0;n<this.sections.length;){let a=this.sections[n++],l=this.sections[n++];l<0?(t(r,i,a),i+=a):i+=l,r+=a}}iterChangedRanges(t,n=!1){sS(this,t,n)}get invertedDesc(){let t=[];for(let n=0;n<this.sections.length;){let r=this.sections[n++],i=this.sections[n++];i<0?t.push(r,i):t.push(i,r)}return new Zi(t)}composeDesc(t){return this.empty?t:t.empty?this:bI(this,t)}mapDesc(t,n=!1){return t.empty?this:uS(this,t,n)}mapPos(t,n=-1,r=Kn.Simple){let i=0,a=0;for(let l=0;l<this.sections.length;){let s=this.sections[l++],c=this.sections[l++],f=i+s;if(c<0){if(f>t)return a+(t-i);a+=s}else{if(r!=Kn.Simple&&f>=t&&(r==Kn.TrackDel&&i<t&&f>t||r==Kn.TrackBefore&&i<t||r==Kn.TrackAfter&&f>t))return null;if(f>t||f==t&&n<0&&!s)return t==i||n<0?a:a+c;a+=c}i=f}if(t>i)throw new RangeError(`Position ${t} is out of range for changeset of length ${i}`);return a}touchesRange(t,n=t){for(let r=0,i=0;r<this.sections.length&&i<=n;){let a=this.sections[r++],l=this.sections[r++],s=i+a;if(l>=0&&i<=n&&s>=t)return i<t&&s>n?"cover":!0;i=s}return!1}toString(){let t="";for(let n=0;n<this.sections.length;){let r=this.sections[n++],i=this.sections[n++];t+=(t?" ":"")+r+(i>=0?":"+i:"")}return t}toJSON(){return this.sections}static fromJSON(t){if(!Array.isArray(t)||t.length%2||t.some(n=>typeof n!="number"))throw new RangeError("Invalid JSON representation of ChangeDesc");return new Zi(t)}static create(t){return new Zi(t)}}class Yt extends Zi{constructor(t,n){super(t),this.inserted=n}apply(t){if(this.length!=t.length)throw new RangeError("Applying change set to a document with the wrong length");return sS(this,(n,r,i,a,l)=>t=t.replace(i,i+(r-n),l),!1),t}mapDesc(t,n=!1){return uS(this,t,n,!0)}invert(t){let n=this.sections.slice(),r=[];for(let i=0,a=0;i<n.length;i+=2){let l=n[i],s=n[i+1];if(s>=0){n[i]=s,n[i+1]=l;let c=i>>1;for(;r.length<c;)r.push(Je.empty);r.push(l?t.slice(a,a+l):Je.empty)}a+=l}return new Yt(n,r)}compose(t){return this.empty?t:t.empty?this:bI(this,t,!0)}map(t,n=!1){return t.empty?this:uS(this,t,n,!0)}iterChanges(t,n=!1){sS(this,t,n)}get desc(){return Zi.create(this.sections)}filter(t){let n=[],r=[],i=[],a=new gh(this);e:for(let l=0,s=0;;){let c=l==t.length?1e9:t[l++];for(;s<c||s==c&&a.len==0;){if(a.done)break e;let d=Math.min(a.len,c-s);Tn(i,d,-1);let p=a.ins==-1?-1:a.off==0?a.ins:0;Tn(n,d,p),p>0&&Ul(r,n,a.text),a.forward(d),s+=d}let f=t[l++];for(;s<f;){if(a.done)break e;let d=Math.min(a.len,f-s);Tn(n,d,-1),Tn(i,d,a.ins==-1?-1:a.off==0?a.ins:0),a.forward(d),s+=d}}return{changes:new Yt(n,r),filtered:Zi.create(i)}}toJSON(){let t=[];for(let n=0;n<this.sections.length;n+=2){let r=this.sections[n],i=this.sections[n+1];i<0?t.push(r):i==0?t.push([r]):t.push([r].concat(this.inserted[n>>1].toJSON()))}return t}static of(t,n,r){let i=[],a=[],l=0,s=null;function c(d=!1){if(!d&&!i.length)return;l<n&&Tn(i,n-l,-1);let p=new Yt(i,a);s=s?s.compose(p.map(s)):p,i=[],a=[],l=0}function f(d){if(Array.isArray(d))for(let p of d)f(p);else if(d instanceof Yt){if(d.length!=n)throw new RangeError(`Mismatched change set length (got ${d.length}, expected ${n})`);c(),s=s?s.compose(d.map(s)):d}else{let{from:p,to:g=p,insert:v}=d;if(p>g||p<0||g>n)throw new RangeError(`Invalid change range ${p} to ${g} (in doc of length ${n})`);let y=v?typeof v=="string"?Je.of(v.split(r||oS)):v:Je.empty,x=y.length;if(p==g&&x==0)return;p<l&&c(),p>l&&Tn(i,p-l,-1),Tn(i,g-p,x),Ul(a,i,y),l=g}}return f(t),c(!s),s}static empty(t){return new Yt(t?[t,-1]:[],[])}static fromJSON(t){if(!Array.isArray(t))throw new RangeError("Invalid JSON representation of ChangeSet");let n=[],r=[];for(let i=0;i<t.length;i++){let a=t[i];if(typeof a=="number")n.push(a,-1);else{if(!Array.isArray(a)||typeof a[0]!="number"||a.some((l,s)=>s&&typeof l!="string"))throw new RangeError("Invalid JSON representation of ChangeSet");if(a.length==1)n.push(a[0],0);else{for(;r.length<i;)r.push(Je.empty);r[i]=Je.of(a.slice(1)),n.push(a[0],r[i].length)}}}return new Yt(n,r)}static createSet(t,n){return new Yt(t,n)}}function Tn(e,t,n,r=!1){if(t==0&&n<=0)return;let i=e.length-2;i>=0&&n<=0&&n==e[i+1]?e[i]+=t:i>=0&&t==0&&e[i]==0?e[i+1]+=n:r?(e[i]+=t,e[i+1]+=n):e.push(t,n)}function Ul(e,t,n){if(n.length==0)return;let r=t.length-2>>1;if(r<e.length)e[e.length-1]=e[e.length-1].append(n);else{for(;e.length<r;)e.push(Je.empty);e.push(n)}}function sS(e,t,n){let r=e.inserted;for(let i=0,a=0,l=0;l<e.sections.length;){let s=e.sections[l++],c=e.sections[l++];if(c<0)i+=s,a+=s;else{let f=i,d=a,p=Je.empty;for(;f+=s,d+=c,c&&r&&(p=p.append(r[l-2>>1])),!(n||l==e.sections.length||e.sections[l+1]<0);)s=e.sections[l++],c=e.sections[l++];t(i,f,a,d,p),i=f,a=d}}}function uS(e,t,n,r=!1){let i=[],a=r?[]:null,l=new gh(e),s=new gh(t);for(let c=-1;;){if(l.done&&s.len||s.done&&l.len)throw new Error("Mismatched change set lengths");if(l.ins==-1&&s.ins==-1){let f=Math.min(l.len,s.len);Tn(i,f,-1),l.forward(f),s.forward(f)}else if(s.ins>=0&&(l.ins<0||c==l.i||l.off==0&&(s.len<l.len||s.len==l.len&&!n))){let f=s.len;for(Tn(i,s.ins,-1);f;){let d=Math.min(l.len,f);l.ins>=0&&c<l.i&&l.len<=d&&(Tn(i,0,l.ins),a&&Ul(a,i,l.text),c=l.i),l.forward(d),f-=d}s.next()}else if(l.ins>=0){let f=0,d=l.len;for(;d;)if(s.ins==-1){let p=Math.min(d,s.len);f+=p,d-=p,s.forward(p)}else if(s.ins==0&&s.len<d)d-=s.len,s.next();else break;Tn(i,f,c<l.i?l.ins:0),a&&c<l.i&&Ul(a,i,l.text),c=l.i,l.forward(l.len-d)}else{if(l.done&&s.done)return a?Yt.createSet(i,a):Zi.create(i);throw new Error("Mismatched change set lengths")}}}function bI(e,t,n=!1){let r=[],i=n?[]:null,a=new gh(e),l=new gh(t);for(let s=!1;;){if(a.done&&l.done)return i?Yt.createSet(r,i):Zi.create(r);if(a.ins==0)Tn(r,a.len,0,s),a.next();else if(l.len==0&&!l.done)Tn(r,0,l.ins,s),i&&Ul(i,r,l.text),l.next();else{if(a.done||l.done)throw new Error("Mismatched change set lengths");{let c=Math.min(a.len2,l.len),f=r.length;if(a.ins==-1){let d=l.ins==-1?-1:l.off?0:l.ins;Tn(r,c,d,s),i&&d&&Ul(i,r,l.text)}else l.ins==-1?(Tn(r,a.off?0:a.len,c,s),i&&Ul(i,r,a.textBit(c))):(Tn(r,a.off?0:a.len,l.off?0:l.ins,s),i&&!l.off&&Ul(i,r,l.text));s=(a.ins>c||l.ins>=0&&l.len>c)&&(s||r.length>f),a.forward2(c),l.forward(c)}}}}class gh{constructor(t){this.set=t,this.i=0,this.next()}next(){let{sections:t}=this.set;this.i<t.length?(this.len=t[this.i++],this.ins=t[this.i++]):(this.len=0,this.ins=-2),this.off=0}get done(){return this.ins==-2}get len2(){return this.ins<0?this.len:this.ins}get text(){let{inserted:t}=this.set,n=this.i-2>>1;return n>=t.length?Je.empty:t[n]}textBit(t){let{inserted:n}=this.set,r=this.i-2>>1;return r>=n.length&&!t?Je.empty:n[r].slice(this.off,t==null?void 0:this.off+t)}forward(t){t==this.len?this.next():(this.len-=t,this.off+=t)}forward2(t){this.ins==-1?this.forward(t):t==this.ins?this.next():(this.ins-=t,this.off+=t)}}class Il{constructor(t,n,r,i){this.from=t,this.to=n,this.flags=r,this.goalColumn=i}get anchor(){return this.flags&32?this.to:this.from}get head(){return this.flags&32?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return this.flags&8?-1:this.flags&16?1:0}get undirectional(){return(this.flags&64)>0}get bidiLevel(){let t=this.flags&7;return t==7?null:t}map(t,n=-1){let r,i;return this.empty?r=i=t.mapPos(this.from,n):(r=t.mapPos(this.from,1),i=t.mapPos(this.to,-1)),r==this.from&&i==this.to?this:new Il(r,i,this.flags,this.goalColumn)}extend(t,n=t,r=0){if(t<=this.anchor&&n>=this.anchor)return te.range(t,n,void 0,void 0,r);let i=Math.abs(t-this.anchor)>Math.abs(n-this.anchor)?t:n;return te.range(this.anchor,i,void 0,void 0,r)}eq(t,n=!1){return this.anchor==t.anchor&&this.head==t.head&&this.goalColumn==t.goalColumn&&(!n||!this.empty||this.assoc==t.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(t){if(!t||typeof t.anchor!="number"||typeof t.head!="number")throw new RangeError("Invalid JSON representation for SelectionRange");return te.range(t.anchor,t.head)}static create(t,n,r,i){return new Il(t,n,r,i)}}class te{constructor(t,n){this.ranges=t,this.mainIndex=n}map(t,n=-1){return t.empty?this:te.create(this.ranges.map(r=>r.map(t,n)),this.mainIndex)}eq(t,n=!1){if(this.ranges.length!=t.ranges.length||this.mainIndex!=t.mainIndex)return!1;for(let r=0;r<this.ranges.length;r++)if(!this.ranges[r].eq(t.ranges[r],n))return!1;return!0}get main(){return this.ranges[this.mainIndex]}asSingle(){return this.ranges.length==1?this:new te([this.main],0)}addRange(t,n=!0){return te.create([t].concat(this.ranges),n?0:this.mainIndex+1)}replaceRange(t,n=this.mainIndex){let r=this.ranges.slice();return r[n]=t,te.create(r,this.mainIndex)}toJSON(){return{ranges:this.ranges.map(t=>t.toJSON()),main:this.mainIndex}}static fromJSON(t){if(!t||!Array.isArray(t.ranges)||typeof t.main!="number"||t.main>=t.ranges.length)throw new RangeError("Invalid JSON representation for EditorSelection");return new te(t.ranges.map(n=>Il.fromJSON(n)),t.main)}static single(t,n=t){return new te([te.range(t,n)],0)}static create(t,n=0){if(t.length==0)throw new RangeError("A selection needs at least one range");for(let r=0,i=0;i<t.length;i++){let a=t[i];if(a.empty?a.from<=r:a.from<r)return te.normalized(t.slice(),n);r=a.to}return new te(t,n)}static cursor(t,n=0,r,i){return Il.create(t,t,(n==0?0:n<0?8:16)|(r==null?7:Math.min(6,r)),i)}static range(t,n,r,i,a){let l=i==null?7:Math.min(6,i);return!a&&t!=n&&(a=n<t?1:-1),a&&(l|=a<0?8:16),n<t?Il.create(n,t,l|32,r):Il.create(t,n,l,r)}static undirectionalRange(t,n){return Il.create(t,n,64,void 0)}static normalized(t,n=0){let r=t[n];t.sort((i,a)=>i.from-a.from),n=t.indexOf(r);for(let i=1;i<t.length;i++){let a=t[i],l=t[i-1];if(a.empty?a.from<=l.to:a.from<l.to){let s=l.from,c=Math.max(a.to,l.to);i<=n&&n--,t.splice(--i,2,a.anchor>a.head?te.range(c,s):te.range(s,c))}}return new te(t,n)}}function xI(e,t){for(let n of e.ranges)if(n.to>t)throw new RangeError("Selection points outside of document")}let TO=0;class me{constructor(t,n,r,i,a){this.combine=t,this.compareInput=n,this.compare=r,this.isStatic=i,this.id=TO++,this.default=t([]),this.extensions=typeof a=="function"?a(this):a}get reader(){return this}static define(t={}){return new me(t.combine||(n=>n),t.compareInput||((n,r)=>n===r),t.compare||(t.combine?(n,r)=>n===r:MO),!!t.static,t.enables)}of(t){return new Bm([],this,0,t)}compute(t,n){if(this.isStatic)throw new Error("Can't compute a static facet");return new Bm(t,this,1,n)}computeN(t,n){if(this.isStatic)throw new Error("Can't compute a static facet");return new Bm(t,this,2,n)}from(t,n){return n||(n=r=>r),this.compute([t],r=>n(r.field(t)))}}function MO(e,t){return e==t||e.length==t.length&&e.every((n,r)=>n===t[r])}class Bm{constructor(t,n,r,i){this.dependencies=t,this.facet=n,this.type=r,this.value=i,this.id=TO++}dynamicSlot(t){var n;let r=this.value,i=this.facet.compareInput,a=this.id,l=t[a]>>1,s=this.type==2,c=!1,f=!1,d=[];for(let p of this.dependencies)p=="doc"?c=!0:p=="selection"?f=!0:(((n=t[p.id])!==null&&n!==void 0?n:1)&1)==0&&d.push(t[p.id]);return{create(p){return p.values[l]=r(p),1},update(p,g){if(c&&g.docChanged||f&&(g.docChanged||g.selection)||cS(p,d)){let v=r(p);if(s?!QM(v,p.values[l],i):!i(v,p.values[l]))return p.values[l]=v,1}return 0},reconfigure:(p,g)=>{let v,y=g.config.address[a];if(y!=null){let x=lg(g,y);if(this.dependencies.every(w=>w instanceof me?g.facet(w)===p.facet(w):w instanceof jn?g.field(w,!1)==p.field(w,!1):!0)||(s?QM(v=r(p),x,i):i(v=r(p),x)))return p.values[l]=x,0}else v=r(p);return p.values[l]=v,1}}}get extension(){return this}}function QM(e,t,n){if(e.length!=t.length)return!1;for(let r=0;r<e.length;r++)if(!n(e[r],t[r]))return!1;return!0}function cS(e,t){let n=!1;for(let r of t)Zf(e,r)&1&&(n=!0);return n}function L7(e,t,n){let r=n.map(c=>e[c.id]),i=n.map(c=>c.type),a=r.filter(c=>!(c&1)),l=e[t.id]>>1;function s(c){let f=[];for(let d=0;d<r.length;d++){let p=lg(c,r[d]);if(i[d]==2)for(let g of p)f.push(g);else f.push(p)}return t.combine(f)}return{create(c){for(let f of r)Zf(c,f);return c.values[l]=s(c),1},update(c,f){if(!cS(c,a))return 0;let d=s(c);return t.compare(d,c.values[l])?0:(c.values[l]=d,1)},reconfigure(c,f){let d=cS(c,r),p=f.config.facets[t.id],g=f.facet(t);if(p&&!d&&MO(n,p))return c.values[l]=g,0;let v=s(c);return t.compare(v,g)?(c.values[l]=g,0):(c.values[l]=v,1)}}}const Vp=me.define({static:!0});class jn{constructor(t,n,r,i,a){this.id=t,this.createF=n,this.updateF=r,this.compareF=i,this.spec=a,this.provides=void 0}static define(t){let n=new jn(TO++,t.create,t.update,t.compare||((r,i)=>r===i),t);return t.provide&&(n.provides=t.provide(n)),n}create(t){let n=t.facet(Vp).find(r=>r.field==this);return(n?.create||this.createF)(t)}slot(t){let n=t[this.id]>>1;return{create:r=>(r.values[n]=this.create(r),1),update:(r,i)=>{let a=r.values[n],l=this.updateF(a,i);return this.compareF(a,l)?0:(r.values[n]=l,1)},reconfigure:(r,i)=>{let a=r.facet(Vp),l=i.facet(Vp),s;return(s=a.find(c=>c.field==this))&&s!=l.find(c=>c.field==this)?(r.values[n]=s.create(r),1):i.config.address[this.id]!=null?(r.values[n]=i.field(this),0):(r.values[n]=this.create(r),1)}}}init(t){return[this,Vp.of({field:this,create:t})]}get extension(){return this}}const Lo={lowest:4,low:3,default:2,high:1,highest:0};function kf(e){return t=>new SI(t,e)}const ys={highest:kf(Lo.highest),high:kf(Lo.high),default:kf(Lo.default),low:kf(Lo.low),lowest:kf(Lo.lowest)};class SI{constructor(t,n){this.inner=t,this.prec=n}get extension(){return this}}class Lv{of(t){return new fS(this,t)}reconfigure(t){return Lv.reconfigure.of({compartment:this,extension:t})}get(t){return t.config.compartments.get(this)}}class fS{constructor(t,n){this.compartment=t,this.inner=n}get extension(){return this}}class ag{constructor(t,n,r,i,a,l){for(this.base=t,this.compartments=n,this.dynamicSlots=r,this.address=i,this.staticValues=a,this.facets=l,this.statusTemplate=[];this.statusTemplate.length<r.length;)this.statusTemplate.push(0)}staticFacet(t){let n=this.address[t.id];return n==null?t.default:this.staticValues[n>>1]}static resolve(t,n,r){let i=[],a=Object.create(null),l=new Map;for(let g of I7(t,n,l))g instanceof jn?i.push(g):(a[g.facet.id]||(a[g.facet.id]=[])).push(g);let s=Object.create(null),c=[],f=[];for(let g of i)s[g.id]=f.length<<1,f.push(v=>g.slot(v));let d=r?.config.facets;for(let g in a){let v=a[g],y=v[0].facet,x=d&&d[g]||[];if(v.every(w=>w.type==0))if(s[y.id]=c.length<<1|1,MO(x,v))c.push(r.facet(y));else{let w=y.combine(v.map(A=>A.value));c.push(r&&y.compare(w,r.facet(y))?r.facet(y):w)}else{for(let w of v)w.type==0?(s[w.id]=c.length<<1|1,c.push(w.value)):(s[w.id]=f.length<<1,f.push(A=>w.dynamicSlot(A)));s[y.id]=f.length<<1,f.push(w=>L7(w,y,v))}}let p=f.map(g=>g(s));return new ag(t,l,p,s,c,a)}}function I7(e,t,n){let r=[[],[],[],[],[]],i=new Map;function a(l,s){let c=i.get(l);if(c!=null){if(c<=s)return;let f=r[c].indexOf(l);f>-1&&r[c].splice(f,1),l instanceof fS&&n.delete(l.compartment)}if(i.set(l,s),Array.isArray(l))for(let f of l)a(f,s);else if(l instanceof fS){if(n.has(l.compartment))throw new RangeError("Duplicate use of compartment in extensions");let f=t.get(l.compartment)||l.inner;n.set(l.compartment,f),a(f,s)}else if(l instanceof SI)a(l.inner,l.prec);else if(l instanceof jn)r[s].push(l),l.provides&&a(l.provides,s);else if(l instanceof Bm)r[s].push(l),l.facet.extensions&&a(l.facet.extensions,Lo.default);else{let f=l.extension;if(!f)throw new Error(`Unrecognized extension value in extension set (${l}).`);if(f==l)throw new Error(`Unrecognized extension value in extension set (${l}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);a(f,s)}}return a(e,Lo.default),r.reduce((l,s)=>l.concat(s))}function Zf(e,t){if(t&1)return 2;let n=t>>1,r=e.status[n];if(r==4)throw new Error("Cyclic dependency between fields and/or facets");if(r&2)return r;e.status[n]=4;let i=e.computeSlot(e,e.config.dynamicSlots[n]);return e.status[n]=2|i}function lg(e,t){return t&1?e.config.staticValues[t>>1]:e.values[t>>1]}const wI=me.define(),hS=me.define({combine:e=>e.some(t=>t),static:!0}),OI=me.define({combine:e=>e.length?e[0]:void 0,static:!0}),AI=me.define(),kI=me.define(),CI=me.define(),EI=me.define({combine:e=>e.length?e[0]:!1});class oa{constructor(t,n){this.type=t,this.value=n}static define(){return new B7}}class B7{of(t){return new oa(this,t)}}class z7{constructor(t){this.map=t}of(t){return new _e(this,t)}}class _e{constructor(t,n){this.type=t,this.value=n}map(t){let n=this.type.map(this.value,t);return n===void 0?void 0:n==this.value?this:new _e(this.type,n)}is(t){return this.type==t}static define(t={}){return new z7(t.map||(n=>n))}static mapEffects(t,n){if(!t.length)return t;let r=[];for(let i of t){let a=i.map(n);a&&r.push(a)}return r}}_e.reconfigure=_e.define();_e.appendConfig=_e.define();class Zt{constructor(t,n,r,i,a,l){this.startState=t,this.changes=n,this.selection=r,this.effects=i,this.annotations=a,this.scrollIntoView=l,this._doc=null,this._state=null,r&&xI(r,n.newLength),a.some(s=>s.type==Zt.time)||(this.annotations=a.concat(Zt.time.of(Date.now())))}static create(t,n,r,i,a,l){return new Zt(t,n,r,i,a,l)}get newDoc(){return this._doc||(this._doc=this.changes.apply(this.startState.doc))}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(t){for(let n of this.annotations)if(n.type==t)return n.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(t){let n=this.annotation(Zt.userEvent);return!!(n&&(n==t||n.length>t.length&&n.slice(0,t.length)==t&&n[t.length]=="."))}}Zt.time=oa.define();Zt.userEvent=oa.define();Zt.addToHistory=oa.define();Zt.remote=oa.define();function $7(e,t){let n=[];for(let r=0,i=0;;){let a,l;if(r<e.length&&(i==t.length||t[i]>=e[r]))a=e[r++],l=e[r++];else if(i<t.length)a=t[i++],l=t[i++];else return n;!n.length||n[n.length-1]<a?n.push(a,l):n[n.length-1]<l&&(n[n.length-1]=l)}}function TI(e,t,n){var r;let i,a,l;return n?(i=t.changes,a=Yt.empty(t.changes.length),l=e.changes.compose(t.changes)):(i=t.changes.map(e.changes),a=e.changes.mapDesc(t.changes,!0),l=e.changes.compose(i)),{changes:l,selection:t.selection?t.selection.map(a):(r=e.selection)===null||r===void 0?void 0:r.map(i),effects:_e.mapEffects(e.effects,i).concat(_e.mapEffects(t.effects,a)),annotations:e.annotations.length?e.annotations.concat(t.annotations):t.annotations,scrollIntoView:e.scrollIntoView||t.scrollIntoView}}function dS(e,t,n){let r=t.selection,i=Nu(t.annotations);return t.userEvent&&(i=i.concat(Zt.userEvent.of(t.userEvent))),{changes:t.changes instanceof Yt?t.changes:Yt.of(t.changes||[],n,e.facet(OI)),selection:r&&(r instanceof te?r:te.single(r.anchor,r.head)),effects:Nu(t.effects),annotations:i,scrollIntoView:!!t.scrollIntoView}}function MI(e,t,n){let r=dS(e,t.length?t[0]:{},e.doc.length);t.length&&t[0].filter===!1&&(n=!1);for(let a=1;a<t.length;a++){t[a].filter===!1&&(n=!1);let l=!!t[a].sequential;r=TI(r,dS(e,t[a],l?r.changes.newLength:e.doc.length),l)}let i=Zt.create(e,r.changes,r.selection,r.effects,r.annotations,r.scrollIntoView);return H7(n?U7(i):i)}function U7(e){let t=e.startState,n=!0;for(let i of t.facet(AI)){let a=i(e);if(a===!1){n=!1;break}Array.isArray(a)&&(n=n===!0?a:$7(n,a))}if(n!==!0){let i,a;if(n===!1)a=e.changes.invertedDesc,i=Yt.empty(t.doc.length);else{let l=e.changes.filter(n);i=l.changes,a=l.filtered.mapDesc(l.changes).invertedDesc}e=Zt.create(t,i,e.selection&&e.selection.map(a),_e.mapEffects(e.effects,a),e.annotations,e.scrollIntoView)}let r=t.facet(kI);for(let i=r.length-1;i>=0;i--){let a=r[i](e);a instanceof Zt?e=a:Array.isArray(a)&&a.length==1&&a[0]instanceof Zt?e=a[0]:e=MI(t,Nu(a),!1)}return e}function H7(e){let t=e.startState,n=t.facet(CI),r=e;for(let i=n.length-1;i>=0;i--){let a=n[i](e);a&&Object.keys(a).length&&(r=TI(r,dS(t,a,e.changes.newLength),!0))}return r==e?e:Zt.create(t,e.changes,e.selection,r.effects,r.annotations,r.scrollIntoView)}const F7=[];function Nu(e){return e==null?F7:Array.isArray(e)?e:[e]}var Ot=(function(e){return e[e.Word=0]="Word",e[e.Space=1]="Space",e[e.Other=2]="Other",e})(Ot||(Ot={}));const q7=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;let pS;try{pS=new RegExp("[\\p{Alphabetic}\\p{Number}_]","u")}catch{}function W7(e){if(pS)return pS.test(e);for(let t=0;t<e.length;t++){let n=e[t];if(/\w/.test(n)||n>""&&(n.toUpperCase()!=n.toLowerCase()||q7.test(n)))return!0}return!1}function V7(e){return t=>{if(!/\S/.test(t))return Ot.Space;if(W7(t))return Ot.Word;for(let n=0;n<e.length;n++)if(t.indexOf(e[n])>-1)return Ot.Word;return Ot.Other}}class Fe{constructor(t,n,r,i,a,l){this.config=t,this.doc=n,this.selection=r,this.values=i,this.status=t.statusTemplate.slice(),this.computeSlot=a,l&&(l._state=this);for(let s=0;s<this.config.dynamicSlots.length;s++)Zf(this,s<<1);this.computeSlot=null}field(t,n=!0){let r=this.config.address[t.id];if(r==null){if(n)throw new RangeError("Field is not present in this state");return}return Zf(this,r),lg(this,r)}update(...t){return MI(this,t,!0)}applyTransaction(t){let n=this.config,{base:r,compartments:i}=n;for(let s of t.effects)s.is(Lv.reconfigure)?(n&&(i=new Map,n.compartments.forEach((c,f)=>i.set(f,c)),n=null),i.set(s.value.compartment,s.value.extension)):s.is(_e.reconfigure)?(n=null,r=s.value):s.is(_e.appendConfig)&&(n=null,r=Nu(r).concat(s.value));let a;n?a=t.startState.values.slice():(n=ag.resolve(r,i,this),a=new Fe(n,this.doc,this.selection,n.dynamicSlots.map(()=>null),(c,f)=>f.reconfigure(c,this),null).values);let l=t.startState.facet(hS)?t.newSelection:t.newSelection.asSingle();new Fe(n,t.newDoc,l,a,(s,c)=>c.update(s,t),t)}replaceSelection(t){return typeof t=="string"&&(t=this.toText(t)),this.changeByRange(n=>({changes:{from:n.from,to:n.to,insert:t},range:te.cursor(n.from+t.length)}))}changeByRange(t){let n=this.selection,r=t(n.ranges[0]),i=this.changes(r.changes),a=[r.range],l=Nu(r.effects);for(let s=1;s<n.ranges.length;s++){let c=t(n.ranges[s]),f=this.changes(c.changes),d=f.map(i);for(let g=0;g<s;g++)a[g]=a[g].map(d);let p=i.mapDesc(f,!0);a.push(c.range.map(p)),i=i.compose(d),l=_e.mapEffects(l,d).concat(_e.mapEffects(Nu(c.effects),p))}return{changes:i,selection:te.create(a,n.mainIndex),effects:l}}changes(t=[]){return t instanceof Yt?t:Yt.of(t,this.doc.length,this.facet(Fe.lineSeparator))}toText(t){return Je.of(t.split(this.facet(Fe.lineSeparator)||oS))}sliceDoc(t=0,n=this.doc.length){return this.doc.sliceString(t,n,this.lineBreak)}facet(t){let n=this.config.address[t.id];return n==null?t.default:(Zf(this,n),lg(this,n))}toJSON(t){let n={doc:this.sliceDoc(),selection:this.selection.toJSON()};if(t)for(let r in t){let i=t[r];i instanceof jn&&this.config.address[i.id]!=null&&(n[r]=i.spec.toJSON(this.field(t[r]),this))}return n}static fromJSON(t,n={},r){if(!t||typeof t.doc!="string")throw new RangeError("Invalid JSON representation for EditorState");let i=[];if(r){for(let a in r)if(Object.prototype.hasOwnProperty.call(t,a)){let l=r[a],s=t[a];i.push(l.init(c=>l.spec.fromJSON(s,c)))}}return Fe.create({doc:t.doc,selection:te.fromJSON(t.selection),extensions:n.extensions?i.concat([n.extensions]):i})}static create(t={}){let n=ag.resolve(t.extensions||[],new Map),r=t.doc instanceof Je?t.doc:Je.of((t.doc||"").split(n.staticFacet(Fe.lineSeparator)||oS)),i=t.selection?t.selection instanceof te?t.selection:te.single(t.selection.anchor,t.selection.head):te.single(0);return xI(i,r.length),n.staticFacet(hS)||(i=i.asSingle()),new Fe(n,r,i,n.dynamicSlots.map(()=>null),(a,l)=>l.create(a),null)}get tabSize(){return this.facet(Fe.tabSize)}get lineBreak(){return this.facet(Fe.lineSeparator)||`
|
package/dist/ui/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>doc2vec</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='0.9em' font-size='90'%3E%F0%9F%93%9A%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-HvEXxtAr.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-Cf1xdzH_.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|