@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/SKILL.md +147 -0
- package/cordis.patch.yml +5 -0
- package/i18n/zh.json +10 -0
- package/lib/client.js +18308 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +6005 -0
- package/lib/types/api.d.ts +620 -0
- package/lib/types/client/KnowledgeAudit.d.ts +39 -0
- package/lib/types/client/KnowledgeContextMenu.d.ts +121 -0
- package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
- package/lib/types/client/KnowledgeFileTree.d.ts +117 -0
- package/lib/types/client/KnowledgeHome.d.ts +102 -0
- package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
- package/lib/types/client/KnowledgeListing.d.ts +174 -0
- package/lib/types/client/KnowledgeLoading.d.ts +41 -0
- package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
- package/lib/types/client/KnowledgePanel.d.ts +58 -0
- package/lib/types/client/KnowledgePermissionOverview.d.ts +147 -0
- package/lib/types/client/KnowledgePermissions.d.ts +47 -0
- package/lib/types/client/KnowledgeProgressPanel.d.ts +53 -0
- package/lib/types/client/KnowledgeReferencePicker.d.ts +81 -0
- package/lib/types/client/KnowledgeSearch.d.ts +83 -0
- package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
- package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
- package/lib/types/client/KnowledgeSettings.d.ts +96 -0
- package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
- package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
- package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
- package/lib/types/client/KnowledgeTrash.d.ts +62 -0
- package/lib/types/client/MenuGlyphs.d.ts +37 -0
- package/lib/types/client/auditPaging.d.ts +40 -0
- package/lib/types/client/departmentFolders.d.ts +57 -0
- package/lib/types/client/deployTarget.d.ts +34 -0
- package/lib/types/client/index.d.ts +40 -0
- package/lib/types/client/insertReadiness.d.ts +60 -0
- package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
- package/lib/types/client/locales.d.ts +505 -0
- package/lib/types/client/openInSidebar.d.ts +62 -0
- package/lib/types/client/ownerLabel.d.ts +39 -0
- package/lib/types/client/paging.d.ts +69 -0
- package/lib/types/client/permissionRows.d.ts +265 -0
- package/lib/types/client/progressRules.d.ts +54 -0
- package/lib/types/client/referenceTargets.d.ts +60 -0
- package/lib/types/client/roleSummary.d.ts +47 -0
- package/lib/types/client/saveAudit.d.ts +87 -0
- package/lib/types/client/shareRelations.d.ts +81 -0
- package/lib/types/client/useFavorites.d.ts +49 -0
- package/lib/types/client/useFileDrop.d.ts +171 -0
- package/lib/types/client/useFileDropHook.d.ts +64 -0
- package/lib/types/client/usePinnedPaths.d.ts +39 -0
- package/lib/types/client/useTreeState.d.ts +46 -0
- package/lib/types/client/useViewMode.d.ts +39 -0
- package/lib/types/deploy.d.ts +209 -0
- package/lib/types/extract.d.ts +144 -0
- package/lib/types/extractionQueue.d.ts +154 -0
- package/lib/types/index.d.ts +899 -0
- package/lib/types/permissions.d.ts +237 -0
- package/lib/types/protocol.d.ts +229 -0
- package/lib/types/remote.d.ts +105 -0
- package/lib/types/trash.d.ts +142 -0
- package/lib/types/viewMetadata.d.ts +20 -0
- package/lib/types/views.d.ts +923 -0
- package/package.json +109 -4
- package/skills/kb-organize/SKILL.md +365 -0
- package/skills/kb-organize/i18n/zh.json +10 -0
- package/README.md +0 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent deployment recipes for the knowledge base.
|
|
3
|
+
*
|
|
4
|
+
* Every step here is a *plan*: a named sequence of shell commands plus the
|
|
5
|
+
* predicate that decides whether the step already ran. The Settings panel shows
|
|
6
|
+
* each step, runs it over the {@link RemoteRunner} seam, and reports the output.
|
|
7
|
+
*
|
|
8
|
+
* Rules these recipes must satisfy:
|
|
9
|
+
* - Re-runnable. Clicking twice must not install twice, start two servers, or
|
|
10
|
+
* fail because something already exists.
|
|
11
|
+
* - Read-only probes first. A status step never mutates anything.
|
|
12
|
+
* - The commands are quoted for a POSIX shell and never interpolate untrusted
|
|
13
|
+
* input without quoting.
|
|
14
|
+
*
|
|
15
|
+
* @module @sparkelf/dsh-knowledge-base/deploy
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* MinerU deployment tiers this plugin can prepare.
|
|
19
|
+
*
|
|
20
|
+
* MinerU 4.x recognises exactly these two (`mineru/types.py`: `DEPLOYMENT_TIERS`), and
|
|
21
|
+
* `models download --tier` validates against that tuple. The `flash` name that also exists in
|
|
22
|
+
* MinerU is a *quality* tier for the parsers that need no local weights, not a deployment
|
|
23
|
+
* tier, so it cannot be prepared and is not offered here.
|
|
24
|
+
*/
|
|
25
|
+
export type MineruTier = 'basic' | 'standard';
|
|
26
|
+
/** Small-model backend for MinerU. */
|
|
27
|
+
export type MineruBackend = 'onnx' | 'torch';
|
|
28
|
+
/** One step of a deployment plan. */
|
|
29
|
+
export interface DeployStep {
|
|
30
|
+
/** Stable id used by the UI. */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Human-facing label key. */
|
|
33
|
+
label: string;
|
|
34
|
+
/**
|
|
35
|
+
* Commands to run in order. Each is a complete shell line.
|
|
36
|
+
* `{{...}}` placeholders are substituted from {@link DeployPlanContext}.
|
|
37
|
+
*/
|
|
38
|
+
commands: string[];
|
|
39
|
+
/** How long this step may take; model downloads are slow. */
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
}
|
|
42
|
+
/** Values substituted into command templates. */
|
|
43
|
+
export interface DeployPlanContext {
|
|
44
|
+
/** Absolute path of the authoritative knowledge tree. */
|
|
45
|
+
actualRoot: string;
|
|
46
|
+
/** Absolute path of the per-user view tree. */
|
|
47
|
+
viewRoot: string;
|
|
48
|
+
/** Tier to deploy. */
|
|
49
|
+
tier: MineruTier;
|
|
50
|
+
/** Small-model backend. */
|
|
51
|
+
backend: MineruBackend;
|
|
52
|
+
/** TCP port the MinerU API listens on. */
|
|
53
|
+
port: number;
|
|
54
|
+
/** Client hosts allowed to mount the NFS export, as an exports spec. */
|
|
55
|
+
permitted: string;
|
|
56
|
+
/** Host serving the export, as `address` or `address:/path` (the pseudo root). */
|
|
57
|
+
server: string;
|
|
58
|
+
/** Where the client mounts the export, i.e. the browsable knowledge tree. */
|
|
59
|
+
mountPoint: string;
|
|
60
|
+
/** Memory ceiling in MiB for the MinerU service; clamped to half the host's RAM. */
|
|
61
|
+
memoryMaxMiB?: number;
|
|
62
|
+
/**
|
|
63
|
+
* How a remote deployment reaches its share.
|
|
64
|
+
*
|
|
65
|
+
* Decides which transport steps {@link deploymentPlan} emits. Absent means `nfs`, the
|
|
66
|
+
* shape this plan had before the protocol existed.
|
|
67
|
+
*/
|
|
68
|
+
protocol?: 'nfs' | 'sshfs';
|
|
69
|
+
/** SSHFS: remote host. */
|
|
70
|
+
sshHost?: string;
|
|
71
|
+
/** SSHFS: login user on the remote host. */
|
|
72
|
+
sshUser?: string;
|
|
73
|
+
/** SSHFS: TCP port the SSH daemon listens on. */
|
|
74
|
+
sshPort?: number;
|
|
75
|
+
/**
|
|
76
|
+
* SSHFS: path of the private key on the HOST, when the credential store resolves to a
|
|
77
|
+
* file. Optional: an ssh-agent-managed key needs no `-i`.
|
|
78
|
+
*/
|
|
79
|
+
sshKeyPath?: string;
|
|
80
|
+
/** SSHFS: absolute path of the shared directory on the remote host. */
|
|
81
|
+
sshRemotePath?: string;
|
|
82
|
+
/** SSHFS: extra `-o` options, appended to the ones the step requires. */
|
|
83
|
+
sshMountOptions?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Memory ceiling for the MinerU service when the config names none.
|
|
87
|
+
*
|
|
88
|
+
* The `basic` tier peaked at 1.86 GB resident while parsing a one-page PDF (measured
|
|
89
|
+
* 2026-10-15), so a ceiling near the steady-state size leaves `MemoryHigh` no headroom. On a
|
|
90
|
+
* host without swap that reads as a stalled parse rather than a slow one, because throttling
|
|
91
|
+
* has nowhere to spill. The service step clamps this to half of RAM, so the number only has to
|
|
92
|
+
* be generous on a roomy machine.
|
|
93
|
+
*/
|
|
94
|
+
export declare const DEFAULT_MINERU_MEMORY_MAX_MIB = 4096;
|
|
95
|
+
/** Rotate a single-quoted shell string safely. */
|
|
96
|
+
export declare function shquote(value: string): string;
|
|
97
|
+
/** Substitute `{{name}}` placeholders; unknown names are left intact. */
|
|
98
|
+
export declare function renderCommand(template: string, context: DeployPlanContext): string;
|
|
99
|
+
/** Marker the image keeps so a redeploy can prove which tier is present. */
|
|
100
|
+
export declare const KB_STATE_FILE = "/var/lib/dataops-kb/state.json";
|
|
101
|
+
/**
|
|
102
|
+
* Read-only environment probe.
|
|
103
|
+
*
|
|
104
|
+
* Establishes which install path is available so the UI can disable the steps
|
|
105
|
+
* that cannot work: a host without Python cannot take the pip path.
|
|
106
|
+
* @returns the probe step.
|
|
107
|
+
*/
|
|
108
|
+
export declare function probeStep(): DeployStep;
|
|
109
|
+
/**
|
|
110
|
+
* Install the MinerU package and download the selected tier's models.
|
|
111
|
+
*
|
|
112
|
+
* The base package (`mineru>=4.0,<5`) is the CPU path: it brings ONNX and
|
|
113
|
+
* llama.cpp but not vLLM, which needs a GPU this environment does not have.
|
|
114
|
+
* @param context - deployment values.
|
|
115
|
+
* @returns the install step.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mineruInstallStep(context: DeployPlanContext): DeployStep;
|
|
118
|
+
/**
|
|
119
|
+
* Install a systemd unit that serves the MinerU HTTP API.
|
|
120
|
+
*
|
|
121
|
+
* A unit rather than a bare background process so the service survives logout
|
|
122
|
+
* and can be restarted by the same click.
|
|
123
|
+
* @param context - deployment values.
|
|
124
|
+
* @returns the service step.
|
|
125
|
+
*/
|
|
126
|
+
export declare function mineruServiceStep(context: DeployPlanContext): DeployStep;
|
|
127
|
+
/**
|
|
128
|
+
* Verify the MinerU API actually answers.
|
|
129
|
+
*
|
|
130
|
+
* This is the check that catches the parameter-compatibility question: the
|
|
131
|
+
* plugin posts multipart form fields while MinerU 4.x reads a request body, so
|
|
132
|
+
* a pass here is the evidence that the two agree.
|
|
133
|
+
* @param context - deployment values.
|
|
134
|
+
* @returns the verification step.
|
|
135
|
+
*/
|
|
136
|
+
export declare function mineruVerifyStep(context: DeployPlanContext): DeployStep;
|
|
137
|
+
/**
|
|
138
|
+
* Deploy the NFS export of the knowledge tree.
|
|
139
|
+
*
|
|
140
|
+
* Uses the container image rather than host packages: Docker is already present
|
|
141
|
+
* on the target, the image carries the userspace tools, and its default export
|
|
142
|
+
* template already includes `no_root_squash`, which hardlinking across NFS
|
|
143
|
+
* requires.
|
|
144
|
+
* @param context - deployment values.
|
|
145
|
+
* @returns the NFS step.
|
|
146
|
+
*/
|
|
147
|
+
export declare function nfsDeployStep(context: DeployPlanContext): DeployStep;
|
|
148
|
+
/**
|
|
149
|
+
* Prove hardlinks work across the NFS export.
|
|
150
|
+
*
|
|
151
|
+
* Creating a hard link between the authoritative tree and a view directory is
|
|
152
|
+
* the single operation the whole permission model depends on, and it is also
|
|
153
|
+
* the operation most likely to fail (wrong export scope, `root_squash`, split
|
|
154
|
+
* filesystems). A pass here is the acceptance test for the storage side.
|
|
155
|
+
* @param context - deployment values.
|
|
156
|
+
* @returns the storage verification step.
|
|
157
|
+
*/
|
|
158
|
+
export declare function nfsVerifyStep(context: DeployPlanContext): DeployStep;
|
|
159
|
+
/**
|
|
160
|
+
* Mount the exported share at the mount point, with the option this deployment needs.
|
|
161
|
+
*
|
|
162
|
+
* `noresvport` is not decorative here. This host runs WSL2 with
|
|
163
|
+
* `networkingMode=mirrored`, where an NFS mount without it hangs until timeout
|
|
164
|
+
* (`mount.nfs: Connection timed out`) while `rpcinfo`, `showmount -e` and a plain TCP
|
|
165
|
+
* connect to 2049 all still succeed. Measured 2026-10-16 against a server that mounts
|
|
166
|
+
* fine from its own kernel, and A/B verified: without the option the mount fails, with
|
|
167
|
+
* it the mount succeeds, repeatably. Microsoft documents the same failure for mirrored
|
|
168
|
+
* networking (WSL issue 12508), which is still open.
|
|
169
|
+
*
|
|
170
|
+
* The NFS client normally opts into a reserved source port, which requires a reply the
|
|
171
|
+
* mirrored networking stack does not deliver; `noresvport` drops that requirement.
|
|
172
|
+
*
|
|
173
|
+
* @param context - Deployment paths and the export address.
|
|
174
|
+
* @returns The mount step.
|
|
175
|
+
*/
|
|
176
|
+
export declare function nfsMountStep(context: DeployPlanContext): DeployStep;
|
|
177
|
+
/**
|
|
178
|
+
* Mount a remote directory over SSHFS on the HOST.
|
|
179
|
+
*
|
|
180
|
+
* The same shape as {@link nfsMountStep}, for a host that cannot mount NFS: the container
|
|
181
|
+
* has no `CAP_SYS_ADMIN` and cannot mount anything itself, so this runs on the Host and the
|
|
182
|
+
* resulting directory is bound in at `mountPoint` like any other.
|
|
183
|
+
*
|
|
184
|
+
* Two details are not incidental:
|
|
185
|
+
* - `StrictHostKeyChecking=accept-new` is limited to this invocation. Accepting an unknown
|
|
186
|
+
* host key globally would silence the one warning that catches a swapped host, so the
|
|
187
|
+
* trust-on-first-use decision is scoped to the connection whose key the operator is
|
|
188
|
+
* establishing.
|
|
189
|
+
* - The reconnect options are not optional here. An SSHFS mount whose connection drops
|
|
190
|
+
* returns EIO to every reader until someone unmounts it; `reconnect` plus a keepalive is
|
|
191
|
+
* what makes a transient network blip a stall instead of a broken knowledge base.
|
|
192
|
+
* @param context - transport and target paths from the deployment config.
|
|
193
|
+
* @returns the mount step.
|
|
194
|
+
*/
|
|
195
|
+
export declare function sshfsMountStep(context: DeployPlanContext): DeployStep;
|
|
196
|
+
/**
|
|
197
|
+
* Verify the SSHFS mount is present, readable, and actually a FUSE mount.
|
|
198
|
+
*
|
|
199
|
+
* Separate from the mount step because "the command exited 0" and "the reader will see
|
|
200
|
+
* files" are different claims: a stale empty directory satisfies the first and not the
|
|
201
|
+
* second, and the filesystem type is what tells them apart.
|
|
202
|
+
* @param context - transport and target paths from the deployment config.
|
|
203
|
+
* @returns the verification step.
|
|
204
|
+
*/
|
|
205
|
+
export declare function sshfsVerifyStep(context: DeployPlanContext): DeployStep;
|
|
206
|
+
/** The full ordered plan the Settings panel presents. */
|
|
207
|
+
export declare function deploymentPlan(context: DeployPlanContext): DeployStep[];
|
|
208
|
+
/** Look up one step by id. */
|
|
209
|
+
export declare function findStep(id: string, context: DeployPlanContext): DeployStep | undefined;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an uploaded document into Markdown so its text becomes greppable.
|
|
3
|
+
*
|
|
4
|
+
* The knowledge tree doubles as a document store and as what an agent greps. A
|
|
5
|
+
* `.docx` on disk is neither: its bytes answer no query. Every upload therefore
|
|
6
|
+
* lands twice — the original, byte for byte, plus a `.md` sibling holding the
|
|
7
|
+
* extracted text — so a reader can still open the real file while a search sees
|
|
8
|
+
* the words inside it.
|
|
9
|
+
*
|
|
10
|
+
* Extraction is delegated rather than implemented here. Three authorities
|
|
11
|
+
* already exist in this deployment and each owns one format family:
|
|
12
|
+
*
|
|
13
|
+
* - MinerU (`/file_parse`) parses PDF and office documents with layout models.
|
|
14
|
+
* It is the best answer for a scanned PDF and the one authority that can
|
|
15
|
+
* recognise tables inside one.
|
|
16
|
+
* - LibreOffice converts the legacy binary formats (`.doc`, `.xls`, `.ppt`) that
|
|
17
|
+
* MinerU and the Python readers refuse.
|
|
18
|
+
* - `python-docx` / `openpyxl` / `python-pptx` / `pypdf` read the modern formats
|
|
19
|
+
* directly, without a service round trip.
|
|
20
|
+
*
|
|
21
|
+
* None of them is reimplemented in TypeScript: a `.docx` is a ZIP of XML and a
|
|
22
|
+
* second parser would only disagree with the first. This module decides WHICH
|
|
23
|
+
* authority to ask and what to do when it cannot answer, which is the part that
|
|
24
|
+
* is specific to this plugin.
|
|
25
|
+
*
|
|
26
|
+
* A failed extraction never fails the upload. The original is already written and
|
|
27
|
+
* remains the source of truth; the caller learns that no `.md` was produced, and
|
|
28
|
+
* the file stays readable through the original.
|
|
29
|
+
*
|
|
30
|
+
* @module @sparkelf/dsh-knowledge-base/extract
|
|
31
|
+
*/
|
|
32
|
+
/** Formats whose text this module knows how to extract. */
|
|
33
|
+
export declare const EXTRACTABLE_EXTENSIONS: readonly [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp", ".rtf"];
|
|
34
|
+
/** One extractable extension. */
|
|
35
|
+
export type ExtractableExtension = typeof EXTRACTABLE_EXTENSIONS[number];
|
|
36
|
+
/** How one request to the extraction authorities ended. */
|
|
37
|
+
export interface ExtractResult {
|
|
38
|
+
/** Markdown text, when extraction produced any. */
|
|
39
|
+
markdown?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Which authority answered. Reported so a bad conversion can be traced.
|
|
42
|
+
*
|
|
43
|
+
* `mineru-ocr` means the first pass classified the document as text and came back
|
|
44
|
+
* too thin to search, so the same parser was asked again with OCR forced. It is a
|
|
45
|
+
* separate value because the two passes can disagree, and a reader debugging a
|
|
46
|
+
* missing line needs to know which one produced the file.
|
|
47
|
+
*/
|
|
48
|
+
via?: 'mineru' | 'mineru-ocr' | 'libreoffice' | 'python' | 'native';
|
|
49
|
+
/** Why no markdown was produced. Present whenever {@link markdown} is absent. */
|
|
50
|
+
reason?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether this path names a document worth extracting.
|
|
54
|
+
*
|
|
55
|
+
* Compared case-insensitively because a browser reports the extension as the
|
|
56
|
+
* user typed it, and a `.PDF` is the same format as a `.pdf`.
|
|
57
|
+
*
|
|
58
|
+
* @param relPath - the uploaded path, relative to the knowledge root.
|
|
59
|
+
* @returns true when {@link extractDocument} has an authority for it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isExtractable(relPath: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The Markdown sibling an extracted document lands beside.
|
|
64
|
+
*
|
|
65
|
+
* Appending rather than replacing the extension keeps two same-named uploads
|
|
66
|
+
* from colliding: `报告.docx` becomes `报告.docx.md`, so it cannot overwrite a
|
|
67
|
+
* `报告.md` the user wrote by hand.
|
|
68
|
+
*
|
|
69
|
+
* @param relPath - the uploaded document's path.
|
|
70
|
+
* @returns the markdown path to write.
|
|
71
|
+
*/
|
|
72
|
+
export declare function markdownSiblingOf(relPath: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Whether MinerU should be asked before the local readers.
|
|
75
|
+
*
|
|
76
|
+
* PDF only: a PDF carries no recoverable structure beyond its layout, which is
|
|
77
|
+
* exactly what MinerU models. An office document still has its XML, and the
|
|
78
|
+
* direct readers keep more of it (a spreadsheet's cell grid survives
|
|
79
|
+
* `openpyxl` and is flattened by a layout parser).
|
|
80
|
+
*
|
|
81
|
+
* @param ext - lowercased extension including the dot.
|
|
82
|
+
* @returns true when the remote parser is the better authority.
|
|
83
|
+
*/
|
|
84
|
+
export declare function prefersRemoteParser(ext: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Ask MinerU to parse one file and return its Markdown.
|
|
87
|
+
*
|
|
88
|
+
* The service answers with a task handle for large files and the body directly
|
|
89
|
+
* for small ones, so both shapes are accepted. A non-200 answer is a miss, not
|
|
90
|
+
* a failure of the upload.
|
|
91
|
+
*
|
|
92
|
+
* @param baseUrl - MinerU service base URL, without a trailing slash.
|
|
93
|
+
* @param fullPath - absolute path of the document on this host.
|
|
94
|
+
* @param fileName - name to report to the service.
|
|
95
|
+
* @returns the Markdown, or undefined when the service could not parse it.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Whether an extraction came back too small to be worth searching.
|
|
99
|
+
*
|
|
100
|
+
* Not a judgement about document quality: a 200-character memo is a real document,
|
|
101
|
+
* and treating it as a failure would re-run OCR over something already correct. The
|
|
102
|
+
* test targets the specific shape of a mis-classified scan -- pages of content that
|
|
103
|
+
* came back as a title and a page number -- so it counts what is left after markup
|
|
104
|
+
* rather than trusting a single threshold on the whole answer.
|
|
105
|
+
*
|
|
106
|
+
* @param markdown - what the parser returned.
|
|
107
|
+
* @returns true when the answer is short enough that a classifier mistake is likely.
|
|
108
|
+
*/
|
|
109
|
+
export declare function looksTooThinToSearch(markdown: string): boolean;
|
|
110
|
+
/** Services and commands extraction may use, as configured for this deployment. */
|
|
111
|
+
export interface ExtractOptions {
|
|
112
|
+
/** MinerU base URL. Empty disables the remote parser. */
|
|
113
|
+
mineruUrl?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract one document to Markdown.
|
|
117
|
+
*
|
|
118
|
+
* Authorities are tried in an order that puts the best answer first for each
|
|
119
|
+
* format, and every step degrades to the next rather than failing: a PDF whose
|
|
120
|
+
* MinerU call times out still gets `pypdf`'s text layer.
|
|
121
|
+
*
|
|
122
|
+
* @param fullPath - absolute path of the written document.
|
|
123
|
+
* @param relPath - the same document's knowledge-relative path, for the title.
|
|
124
|
+
* @param options - services this deployment exposes.
|
|
125
|
+
* @returns the Markdown and the authority that produced it, or the reason none did.
|
|
126
|
+
*/
|
|
127
|
+
export declare function extractDocument(fullPath: string, relPath: string, options?: ExtractOptions): Promise<ExtractResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Whether a file exists and is a regular file.
|
|
130
|
+
*
|
|
131
|
+
* @param path - absolute path to test.
|
|
132
|
+
* @returns true when it is there.
|
|
133
|
+
*/
|
|
134
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
135
|
+
/**
|
|
136
|
+
* Write extracted markdown beside its document.
|
|
137
|
+
*
|
|
138
|
+
* Kept separate from {@link extractDocument} so the write is testable without an
|
|
139
|
+
* extraction service.
|
|
140
|
+
*
|
|
141
|
+
* @param markdownPath - absolute path of the `.md` sibling.
|
|
142
|
+
* @param markdown - the text to write.
|
|
143
|
+
*/
|
|
144
|
+
export declare function writeExtractedMarkdown(markdownPath: string, markdown: string): Promise<void>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background extraction of a document's Markdown sibling.
|
|
3
|
+
*
|
|
4
|
+
* Upload and extraction are two different promises. The bytes are the uploader's
|
|
5
|
+
* deliverable and must be visible the moment the write lands; the `.md` sibling is
|
|
6
|
+
* derived text that may take a minute (a scanned PDF goes through MinerU, a legacy
|
|
7
|
+
* `.doc` through LibreOffice). Doing both inside the upload request made a slow
|
|
8
|
+
* parse look like a slow upload -- and a failed parse look like a failed upload.
|
|
9
|
+
*
|
|
10
|
+
* This queue separates them. `enqueue` never blocks and never throws: a failed
|
|
11
|
+
* extraction is a status, not an upload error. The queue lives in this process only
|
|
12
|
+
* (see {@link MAX_FINISHED_TASKS}) because the file itself is already durable; a
|
|
13
|
+
* restart loses the bookkeeping, and the manual `extract.retry` route is the recovery
|
|
14
|
+
* path. Persisting a queue would add a second source of truth about files that the
|
|
15
|
+
* filesystem already answers for.
|
|
16
|
+
*
|
|
17
|
+
* @module @sparkelf/dsh-knowledge-base/extractionQueue
|
|
18
|
+
*/
|
|
19
|
+
/** Lifecycle of one extraction task. */
|
|
20
|
+
export type ExtractionState = 'pending' | 'running' | 'succeeded' | 'failed' | 'unsupported';
|
|
21
|
+
/** One extraction task, as a reader observes it. */
|
|
22
|
+
export interface ExtractionTask {
|
|
23
|
+
/** Knowledge-relative path of the SOURCE document. */
|
|
24
|
+
relPath: string;
|
|
25
|
+
/** Where the task is in its lifecycle. */
|
|
26
|
+
state: ExtractionState;
|
|
27
|
+
/** When the task was accepted, ISO-8601. */
|
|
28
|
+
queuedAt: string;
|
|
29
|
+
/** When the run started, ISO-8601. */
|
|
30
|
+
startedAt?: string;
|
|
31
|
+
/** When the run reached a terminal state, ISO-8601. */
|
|
32
|
+
finishedAt?: string;
|
|
33
|
+
/** The Markdown sibling that was written, when one was. */
|
|
34
|
+
markdown?: string;
|
|
35
|
+
/** Which authority produced the text (`mineru`, `libreoffice`, `python`, `native`). */
|
|
36
|
+
via?: string;
|
|
37
|
+
/** Why the extraction produced nothing, verbatim from the extractor. */
|
|
38
|
+
reason?: string;
|
|
39
|
+
}
|
|
40
|
+
/** What one run reports back to the queue. */
|
|
41
|
+
export interface ExtractionOutcome {
|
|
42
|
+
/** Terminal state for this run. */
|
|
43
|
+
state: Exclude<ExtractionState, 'pending' | 'running'>;
|
|
44
|
+
/** The written sibling, when the run produced one. */
|
|
45
|
+
markdown?: string;
|
|
46
|
+
/** The authority that produced the text. */
|
|
47
|
+
via?: string;
|
|
48
|
+
/** Why nothing was produced. */
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* How many extractions may run at once.
|
|
53
|
+
*
|
|
54
|
+
* One, deliberately. Every authority this drives is expensive in a way a second
|
|
55
|
+
* concurrent run makes worse: LibreOffice spawns a headless process per conversion and
|
|
56
|
+
* MinerU parses on the same small box the workspace already shares. Serializing also
|
|
57
|
+
* makes the observable order the submission order, which is what a reader retrying one
|
|
58
|
+
* file expects.
|
|
59
|
+
*/
|
|
60
|
+
export declare const MAX_CONCURRENT_EXTRACTIONS = 1;
|
|
61
|
+
/**
|
|
62
|
+
* Most finished tasks remembered for status reads.
|
|
63
|
+
*
|
|
64
|
+
* The map is bookkeeping, not storage: an evicted entry loses only its status line,
|
|
65
|
+
* never a file. Bounded so a long-lived process does not accumulate one record per
|
|
66
|
+
* upload it ever saw. Running and pending tasks are never evicted.
|
|
67
|
+
*/
|
|
68
|
+
export declare const MAX_FINISHED_TASKS = 200;
|
|
69
|
+
/** What the queue needs from the surrounding plugin, injected so it stays testable. */
|
|
70
|
+
export interface ExtractionQueueOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Run one extraction.
|
|
73
|
+
*
|
|
74
|
+
* Supplied by the plugin so this module owns scheduling only, not the extraction
|
|
75
|
+
* itself: the caller keeps the permission-checked `extractBeside` path in one place.
|
|
76
|
+
* @param relPath - the source document.
|
|
77
|
+
* @param mineruBaseUrl - the caller's own parser address, when set.
|
|
78
|
+
* @returns the terminal state of the run.
|
|
79
|
+
*/
|
|
80
|
+
run: (relPath: string, mineruBaseUrl: string | undefined) => Promise<ExtractionOutcome>;
|
|
81
|
+
/**
|
|
82
|
+
* Whether this path can be extracted at all.
|
|
83
|
+
*
|
|
84
|
+
* Asked before queueing so an unsupported file answers immediately rather than
|
|
85
|
+
* occupying the single worker slot to discover it has no reader.
|
|
86
|
+
*/
|
|
87
|
+
isExtractable: (relPath: string) => boolean;
|
|
88
|
+
}
|
|
89
|
+
/** One extraction queue. One instance per plugin registration. */
|
|
90
|
+
export interface ExtractionQueue {
|
|
91
|
+
/**
|
|
92
|
+
* Accept a source document for background extraction.
|
|
93
|
+
*
|
|
94
|
+
* Never throws and never waits: the upload that calls this has already written its
|
|
95
|
+
* bytes, and nothing about a parse may change that answer. A repeat for the same
|
|
96
|
+
* path while one is still pending or running returns the existing task instead of
|
|
97
|
+
* queueing a second run.
|
|
98
|
+
* @param relPath - the source document, relative to the knowledge root.
|
|
99
|
+
* @param mineruBaseUrl - the caller's own parser address, when set.
|
|
100
|
+
* @returns the task as it stands right after the call.
|
|
101
|
+
*/
|
|
102
|
+
enqueue: (relPath: string, mineruBaseUrl?: string) => ExtractionTask;
|
|
103
|
+
/**
|
|
104
|
+
* Read one task.
|
|
105
|
+
* @param relPath - the source document.
|
|
106
|
+
* @returns the task, or undefined when this process never saw it.
|
|
107
|
+
*/
|
|
108
|
+
get: (relPath: string) => ExtractionTask | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Snapshot the queue for a status surface.
|
|
111
|
+
* @returns every remembered task plus what is running.
|
|
112
|
+
*/
|
|
113
|
+
snapshot: () => {
|
|
114
|
+
running: string | undefined;
|
|
115
|
+
size: number;
|
|
116
|
+
tasks: ExtractionTask[];
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Wait for one path's task to reach a terminal state.
|
|
120
|
+
*
|
|
121
|
+
* The manual-retry route waits on its own document, and it must wait on the TASK
|
|
122
|
+
* rather than on `drain()`. Two reasons, both about there being one truth:
|
|
123
|
+
*
|
|
124
|
+
* 1. `drain()` also waits for unrelated uploads queued ahead, so a retry would
|
|
125
|
+
* report a delay that has nothing to do with this file.
|
|
126
|
+
* 2. The retry must NOT run its own extraction and then enqueue a second copy to
|
|
127
|
+
* keep the status surface current. That is two runs of one document, and an
|
|
128
|
+
* answer built from one run while `extract.status` reports the other -- the
|
|
129
|
+
* exact split this method exists to remove.
|
|
130
|
+
*
|
|
131
|
+
* Resolves with the terminal task, or immediately with `undefined` when this
|
|
132
|
+
* process never queued that path (a restart, or a path it has not seen). Never
|
|
133
|
+
* rejects: the caller is a route, and the queue is not allowed to become an error
|
|
134
|
+
* path for an upload.
|
|
135
|
+
* @param relPath - the source document.
|
|
136
|
+
* @returns the terminal task, or undefined when the path was never queued.
|
|
137
|
+
*/
|
|
138
|
+
settled: (relPath: string) => Promise<ExtractionTask | undefined>;
|
|
139
|
+
/**
|
|
140
|
+
* Wait for the queue to go idle.
|
|
141
|
+
*
|
|
142
|
+
* For tests and for a caller that asked to wait on one specific run; a reader never
|
|
143
|
+
* needs it. Resolves when nothing is pending or running.
|
|
144
|
+
* @returns when the worker has drained.
|
|
145
|
+
*/
|
|
146
|
+
drain: () => Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create an extraction queue.
|
|
150
|
+
*
|
|
151
|
+
* @param options - the runner and the predicate that decides whether a path has a reader.
|
|
152
|
+
* @returns a queue that runs at most {@link MAX_CONCURRENT_EXTRACTIONS} task at a time.
|
|
153
|
+
*/
|
|
154
|
+
export declare function createExtractionQueue(options: ExtractionQueueOptions): ExtractionQueue;
|