@convex-dev/rag 0.1.7
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 +201 -0
- package/README.md +371 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/defaultChunker.d.ts +15 -0
- package/dist/client/defaultChunker.d.ts.map +1 -0
- package/dist/client/defaultChunker.js +148 -0
- package/dist/client/defaultChunker.js.map +1 -0
- package/dist/client/fileUtils.d.ts +24 -0
- package/dist/client/fileUtils.d.ts.map +1 -0
- package/dist/client/fileUtils.js +179 -0
- package/dist/client/fileUtils.js.map +1 -0
- package/dist/client/index.d.ts +442 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +597 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +29 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +439 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +22 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +60 -0
- package/dist/component/_generated/server.d.ts +149 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +74 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/chunks.d.ts +139 -0
- package/dist/component/chunks.d.ts.map +1 -0
- package/dist/component/chunks.js +413 -0
- package/dist/component/chunks.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +6 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/embeddings/importance.d.ts +21 -0
- package/dist/component/embeddings/importance.d.ts.map +1 -0
- package/dist/component/embeddings/importance.js +67 -0
- package/dist/component/embeddings/importance.js.map +1 -0
- package/dist/component/embeddings/index.d.ts +23 -0
- package/dist/component/embeddings/index.d.ts.map +1 -0
- package/dist/component/embeddings/index.js +54 -0
- package/dist/component/embeddings/index.js.map +1 -0
- package/dist/component/embeddings/tables.d.ts +39 -0
- package/dist/component/embeddings/tables.d.ts.map +1 -0
- package/dist/component/embeddings/tables.js +53 -0
- package/dist/component/embeddings/tables.js.map +1 -0
- package/dist/component/entries.d.ts +167 -0
- package/dist/component/entries.d.ts.map +1 -0
- package/dist/component/entries.js +409 -0
- package/dist/component/entries.js.map +1 -0
- package/dist/component/filters.d.ts +46 -0
- package/dist/component/filters.d.ts.map +1 -0
- package/dist/component/filters.js +72 -0
- package/dist/component/filters.js.map +1 -0
- package/dist/component/namespaces.d.ts +131 -0
- package/dist/component/namespaces.d.ts.map +1 -0
- package/dist/component/namespaces.js +222 -0
- package/dist/component/namespaces.js.map +1 -0
- package/dist/component/schema.d.ts +1697 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +88 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/search.d.ts +20 -0
- package/dist/component/search.d.ts.map +1 -0
- package/dist/component/search.js +69 -0
- package/dist/component/search.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/shared.d.ts +479 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +98 -0
- package/dist/shared.js.map +1 -0
- package/package.json +97 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/defaultChunker.test.ts +243 -0
- package/src/client/defaultChunker.ts +183 -0
- package/src/client/fileUtils.ts +179 -0
- package/src/client/index.test.ts +475 -0
- package/src/client/index.ts +1125 -0
- package/src/client/setup.test.ts +28 -0
- package/src/client/types.ts +69 -0
- package/src/component/_generated/api.d.ts +439 -0
- package/src/component/_generated/api.js +23 -0
- package/src/component/_generated/dataModel.d.ts +60 -0
- package/src/component/_generated/server.d.ts +149 -0
- package/src/component/_generated/server.js +90 -0
- package/src/component/chunks.test.ts +915 -0
- package/src/component/chunks.ts +555 -0
- package/src/component/convex.config.ts +7 -0
- package/src/component/embeddings/importance.test.ts +249 -0
- package/src/component/embeddings/importance.ts +75 -0
- package/src/component/embeddings/index.test.ts +482 -0
- package/src/component/embeddings/index.ts +99 -0
- package/src/component/embeddings/tables.ts +114 -0
- package/src/component/entries.test.ts +341 -0
- package/src/component/entries.ts +546 -0
- package/src/component/filters.ts +119 -0
- package/src/component/namespaces.ts +299 -0
- package/src/component/schema.ts +106 -0
- package/src/component/search.test.ts +445 -0
- package/src/component/search.ts +97 -0
- package/src/component/setup.test.ts +5 -0
- package/src/react/index.ts +7 -0
- package/src/shared.ts +247 -0
- package/src/vitest.config.ts +7 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunk text for embedding.
|
|
3
|
+
*
|
|
4
|
+
* By default, it will chunk into paragraphs and target
|
|
5
|
+
* 200-2000 characters per chunk (only less than 1 line if the hard limit is reached).
|
|
6
|
+
*/
|
|
7
|
+
export function defaultChunker(text, { minLines = 1, minCharsSoftLimit = 200, maxCharsSoftLimit = 2000, maxCharsHardLimit = 10000, delimiter = "\n\n", } = {}) {
|
|
8
|
+
if (!text)
|
|
9
|
+
return [];
|
|
10
|
+
// Split text into individual lines
|
|
11
|
+
const lines = text.split("\n");
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let currentChunk = [];
|
|
14
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15
|
+
const line = lines[i];
|
|
16
|
+
// Check if this line starts a new section (based on delimiter pattern)
|
|
17
|
+
const isNewSection = shouldStartNewSection(lines, i, delimiter);
|
|
18
|
+
// Calculate potential chunk if we add this line
|
|
19
|
+
const potentialChunk = [...currentChunk, line].join("\n");
|
|
20
|
+
// If adding this line would exceed max chars, finalize current chunk first
|
|
21
|
+
if (potentialChunk.length > maxCharsSoftLimit && currentChunk.length > 0) {
|
|
22
|
+
const trimmedChunk = removeTrailingEmptyLines(currentChunk);
|
|
23
|
+
chunks.push(trimmedChunk.join("\n"));
|
|
24
|
+
// Split the line if it exceeds hard limit
|
|
25
|
+
const splitLines = maybeSplitLine(line, maxCharsHardLimit);
|
|
26
|
+
// Add all but the last split piece as separate chunks
|
|
27
|
+
for (let j = 0; j < splitLines.length - 1; j++) {
|
|
28
|
+
chunks.push(splitLines[j]);
|
|
29
|
+
}
|
|
30
|
+
// Keep the last piece for potential combination with next lines
|
|
31
|
+
currentChunk = [splitLines[splitLines.length - 1]];
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
// If we're starting a new section and current chunk meets minimum requirements
|
|
35
|
+
if (isNewSection &&
|
|
36
|
+
currentChunk.length >= minLines &&
|
|
37
|
+
currentChunk.join("\n").length >= Math.min(minCharsSoftLimit * 0.8, 150)) {
|
|
38
|
+
// Simple logic: only split if potential chunk would exceed the soft max limit
|
|
39
|
+
if (potentialChunk.length > maxCharsSoftLimit) {
|
|
40
|
+
// When splitting at delimiter boundary, preserve natural empty lines (don't remove trailing empty lines)
|
|
41
|
+
chunks.push(currentChunk.join("\n"));
|
|
42
|
+
currentChunk = [line];
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Add line to current chunk
|
|
47
|
+
currentChunk.push(line);
|
|
48
|
+
// If current chunk is too big, split it
|
|
49
|
+
if (currentChunk.join("\n").length > maxCharsSoftLimit) {
|
|
50
|
+
if (currentChunk.length === 1) {
|
|
51
|
+
// Single line too long - split it if it exceeds hard limit
|
|
52
|
+
const splitLines = maybeSplitLine(line, maxCharsHardLimit);
|
|
53
|
+
if (splitLines.length > 1) {
|
|
54
|
+
// Line was split - add all but the last piece as separate chunks
|
|
55
|
+
for (let j = 0; j < splitLines.length - 1; j++) {
|
|
56
|
+
chunks.push(splitLines[j]);
|
|
57
|
+
}
|
|
58
|
+
// Keep the last piece for potential combination with next lines
|
|
59
|
+
currentChunk = [splitLines[splitLines.length - 1]];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Line doesn't exceed hard limit, keep it as is
|
|
63
|
+
chunks.push(line);
|
|
64
|
+
currentChunk = [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Remove last line and finalize chunk
|
|
69
|
+
const lastLine = currentChunk.pop();
|
|
70
|
+
const trimmedChunk = removeTrailingEmptyLines(currentChunk);
|
|
71
|
+
chunks.push(trimmedChunk.join("\n"));
|
|
72
|
+
currentChunk = [lastLine];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Add remaining chunk, splitting if it exceeds hard limit
|
|
77
|
+
if (currentChunk.length > 0) {
|
|
78
|
+
const remainingText = currentChunk.join("\n");
|
|
79
|
+
if (remainingText.length > maxCharsHardLimit) {
|
|
80
|
+
// Split the remaining chunk if it exceeds hard limit
|
|
81
|
+
const splitLines = maybeSplitLine(remainingText, maxCharsHardLimit);
|
|
82
|
+
chunks.push(...splitLines);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const trimmedChunk = removeTrailingEmptyLines(currentChunk);
|
|
86
|
+
chunks.push(trimmedChunk.join("\n"));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return chunks;
|
|
90
|
+
}
|
|
91
|
+
function maybeSplitLine(line, maxCharsHardLimit) {
|
|
92
|
+
const inputs = [line]; // in reverse order
|
|
93
|
+
const lines = [];
|
|
94
|
+
while (inputs.length > 0) {
|
|
95
|
+
const input = inputs.pop();
|
|
96
|
+
if (input.length <= maxCharsHardLimit) {
|
|
97
|
+
lines.push(input);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// split it in half
|
|
101
|
+
const splitIndex = Math.floor(input.length / 2);
|
|
102
|
+
const candidate = input.slice(0, splitIndex);
|
|
103
|
+
const rest = input.slice(splitIndex);
|
|
104
|
+
if (candidate.length < maxCharsHardLimit) {
|
|
105
|
+
lines.push(candidate, rest);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
inputs.push(rest, candidate);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return lines;
|
|
112
|
+
}
|
|
113
|
+
function shouldStartNewSection(lines, index, delimiter) {
|
|
114
|
+
if (index === 0)
|
|
115
|
+
return false;
|
|
116
|
+
// For default "\n\n" delimiter, check for blank lines
|
|
117
|
+
if (delimiter === "\n\n") {
|
|
118
|
+
return lines[index - 1] === "";
|
|
119
|
+
}
|
|
120
|
+
// For custom delimiters, check if previous lines match the delimiter pattern
|
|
121
|
+
const delimiterLines = delimiter.split("\n");
|
|
122
|
+
if (delimiterLines.length <= 1)
|
|
123
|
+
return false;
|
|
124
|
+
// Check if the delimiter pattern appears before this line
|
|
125
|
+
for (let i = 0; i < delimiterLines.length - 1; i++) {
|
|
126
|
+
const checkIndex = index - delimiterLines.length + 1 + i;
|
|
127
|
+
if (checkIndex < 0 || lines[checkIndex] !== delimiterLines[i]) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function removeTrailingEmptyLines(lines) {
|
|
134
|
+
// Don't remove anything if there's only one line
|
|
135
|
+
if (lines.length <= 1) {
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
// Find the last non-empty line
|
|
139
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
140
|
+
if (lines[i].trim() !== "") {
|
|
141
|
+
return lines.slice(0, i + 1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// If all lines are empty, keep at least one
|
|
145
|
+
return lines.length > 0 ? [lines[0]] : [];
|
|
146
|
+
}
|
|
147
|
+
export default defaultChunker;
|
|
148
|
+
//# sourceMappingURL=defaultChunker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultChunker.js","sourceRoot":"","sources":["../../src/client/defaultChunker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function guessMimeTypeFromExtension(filename: string): string | undefined;
|
|
2
|
+
/**
|
|
3
|
+
* Return a best-guess MIME type based on the magic-number signature
|
|
4
|
+
* found at the start of an ArrayBuffer.
|
|
5
|
+
*
|
|
6
|
+
* @param buf – the source ArrayBuffer
|
|
7
|
+
* @returns the detected MIME type, or `"application/octet-stream"` if unknown
|
|
8
|
+
*/
|
|
9
|
+
export declare function guessMimeTypeFromContents(buf: ArrayBuffer | string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Make a contentHash of a Blob that matches the File Storage metadata, allowing
|
|
12
|
+
* identifying when content is identical.
|
|
13
|
+
* @param blob The contents to hash
|
|
14
|
+
* @returns sha256 hash of the contents
|
|
15
|
+
*/
|
|
16
|
+
export declare function contentHashFromArrayBuffer(buffer: ArrayBuffer): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Split a filename into a keyword-friendly string. Specifically adds sections
|
|
19
|
+
* of camelCase and TitleCase into a space-separated strings.
|
|
20
|
+
* e.g. "MyFile is soGreat.txt" -> "MyFile is soGreat.txt My File so Great"
|
|
21
|
+
* Note: it doesn't split up titles that don't have a file extension.
|
|
22
|
+
*/
|
|
23
|
+
export declare function splitFilename(title: string | undefined): string | undefined;
|
|
24
|
+
//# sourceMappingURL=fileUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileUtils.d.ts","sourceRoot":"","sources":["../../src/client/fileUtils.ts"],"names":[],"mappings":"AAAA,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CA6DpB;AACD;;;;;;GAMG;AAEH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,CA2D3E;AACD;;;;;GAKG;AAEH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,WAAW,mBAMnE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAyB3E"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
export function guessMimeTypeFromExtension(filename) {
|
|
2
|
+
const extension = filename.split(".").pop();
|
|
3
|
+
if (!extension || extension.includes(" ")) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
switch (extension.toLowerCase()) {
|
|
7
|
+
case "pdf":
|
|
8
|
+
return "application/pdf";
|
|
9
|
+
case "txt":
|
|
10
|
+
case "rtf":
|
|
11
|
+
return "text/plain";
|
|
12
|
+
case "json":
|
|
13
|
+
return "application/json";
|
|
14
|
+
case "xml":
|
|
15
|
+
return "application/xml";
|
|
16
|
+
case "html":
|
|
17
|
+
return "text/html";
|
|
18
|
+
case "css":
|
|
19
|
+
return "text/css";
|
|
20
|
+
case "js":
|
|
21
|
+
case "cjs":
|
|
22
|
+
case "mjs":
|
|
23
|
+
case "jsx":
|
|
24
|
+
case "ts":
|
|
25
|
+
case "tsx":
|
|
26
|
+
return "text/javascript";
|
|
27
|
+
case "md":
|
|
28
|
+
case "mdx":
|
|
29
|
+
return "text/markdown";
|
|
30
|
+
case "csv":
|
|
31
|
+
return "text/csv";
|
|
32
|
+
case "zip":
|
|
33
|
+
return "application/zip";
|
|
34
|
+
case "apng":
|
|
35
|
+
return "image/apng";
|
|
36
|
+
case "png":
|
|
37
|
+
return "image/png";
|
|
38
|
+
case "avif":
|
|
39
|
+
return "image/avif";
|
|
40
|
+
case "gif":
|
|
41
|
+
return "image/gif";
|
|
42
|
+
case "svg":
|
|
43
|
+
return "image/svg+xml";
|
|
44
|
+
case "webp":
|
|
45
|
+
return "image/webp";
|
|
46
|
+
case "tiff":
|
|
47
|
+
return "image/tiff";
|
|
48
|
+
case "ico":
|
|
49
|
+
return "image/x-icon";
|
|
50
|
+
case "jpeg":
|
|
51
|
+
case "jpg":
|
|
52
|
+
return "image/jpeg";
|
|
53
|
+
case "mp1":
|
|
54
|
+
case "mp2":
|
|
55
|
+
case "mp3":
|
|
56
|
+
return "audio/mpeg";
|
|
57
|
+
case "mp4":
|
|
58
|
+
return "video/mp4";
|
|
59
|
+
default:
|
|
60
|
+
return "application/octet-stream";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Return a best-guess MIME type based on the magic-number signature
|
|
65
|
+
* found at the start of an ArrayBuffer.
|
|
66
|
+
*
|
|
67
|
+
* @param buf – the source ArrayBuffer
|
|
68
|
+
* @returns the detected MIME type, or `"application/octet-stream"` if unknown
|
|
69
|
+
*/
|
|
70
|
+
export function guessMimeTypeFromContents(buf) {
|
|
71
|
+
if (typeof buf === "string") {
|
|
72
|
+
if (buf.match(/^data:\w+\/\w+;base64/)) {
|
|
73
|
+
return buf.split(";")[0].split(":")[1];
|
|
74
|
+
}
|
|
75
|
+
return "text/plain";
|
|
76
|
+
}
|
|
77
|
+
if (buf.byteLength < 4)
|
|
78
|
+
return "application/octet-stream";
|
|
79
|
+
// Read the first 12 bytes (enough for all signatures below)
|
|
80
|
+
const bytes = new Uint8Array(buf.slice(0, 12));
|
|
81
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
82
|
+
// Helper so we can look at only the needed prefix
|
|
83
|
+
const startsWith = (sig) => hex.startsWith(sig.toLowerCase());
|
|
84
|
+
// --- image formats ---
|
|
85
|
+
if (startsWith("89504e47"))
|
|
86
|
+
return "image/png"; // PNG - 89 50 4E 47
|
|
87
|
+
if (startsWith("ffd8ffdb") ||
|
|
88
|
+
startsWith("ffd8ffe0") ||
|
|
89
|
+
startsWith("ffd8ffee") ||
|
|
90
|
+
startsWith("ffd8ffe1"))
|
|
91
|
+
return "image/jpeg"; // JPEG
|
|
92
|
+
if (startsWith("47494638"))
|
|
93
|
+
return "image/gif"; // GIF
|
|
94
|
+
if (startsWith("424d"))
|
|
95
|
+
return "image/bmp"; // BMP
|
|
96
|
+
if (startsWith("52494646") && hex.substr(16, 8) === "57454250")
|
|
97
|
+
return "image/webp"; // WEBP (RIFF....WEBP)
|
|
98
|
+
if (startsWith("49492a00"))
|
|
99
|
+
return "image/tiff"; // TIFF
|
|
100
|
+
// <svg in hex is 3c 3f 78 6d 6c
|
|
101
|
+
if (startsWith("3c737667"))
|
|
102
|
+
return "image/svg+xml"; // <svg
|
|
103
|
+
if (startsWith("3c3f786d"))
|
|
104
|
+
return "image/svg+xml"; // <?xm
|
|
105
|
+
// --- audio/video ---
|
|
106
|
+
if (startsWith("494433"))
|
|
107
|
+
return "audio/mpeg"; // MP3 (ID3)
|
|
108
|
+
if (startsWith("000001ba") || startsWith("000001b3"))
|
|
109
|
+
return "video/mpeg"; // MPEG container
|
|
110
|
+
if (startsWith("1a45dfa3"))
|
|
111
|
+
return "video/webm"; // WEBM / Matroska
|
|
112
|
+
if (startsWith("00000018") && hex.substr(16, 8) === "66747970")
|
|
113
|
+
return "video/mp4"; // MP4
|
|
114
|
+
if (startsWith("4f676753"))
|
|
115
|
+
return "audio/ogg"; // OGG / Opus
|
|
116
|
+
// --- documents & archives ---
|
|
117
|
+
if (startsWith("25504446"))
|
|
118
|
+
return "application/pdf"; // PDF
|
|
119
|
+
if (startsWith("504b0304") ||
|
|
120
|
+
startsWith("504b0506") ||
|
|
121
|
+
startsWith("504b0708"))
|
|
122
|
+
return "application/zip"; // ZIP / DOCX / PPTX / XLSX / EPUB
|
|
123
|
+
if (startsWith("52617221"))
|
|
124
|
+
return "application/x-rar-compressed"; // RAR
|
|
125
|
+
if (startsWith("7f454c46"))
|
|
126
|
+
return "application/x-elf"; // ELF binaries
|
|
127
|
+
if (startsWith("1f8b08"))
|
|
128
|
+
return "application/gzip"; // GZIP
|
|
129
|
+
if (startsWith("425a68"))
|
|
130
|
+
return "application/x-bzip2"; // BZIP2
|
|
131
|
+
if (startsWith("3c3f786d6c"))
|
|
132
|
+
return "application/xml"; // XML
|
|
133
|
+
// Plain text, JSON and others are trickier—fallback:
|
|
134
|
+
return "application/octet-stream";
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Make a contentHash of a Blob that matches the File Storage metadata, allowing
|
|
138
|
+
* identifying when content is identical.
|
|
139
|
+
* @param blob The contents to hash
|
|
140
|
+
* @returns sha256 hash of the contents
|
|
141
|
+
*/
|
|
142
|
+
export async function contentHashFromArrayBuffer(buffer) {
|
|
143
|
+
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", buffer)))
|
|
144
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
145
|
+
.join("");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Split a filename into a keyword-friendly string. Specifically adds sections
|
|
149
|
+
* of camelCase and TitleCase into a space-separated strings.
|
|
150
|
+
* e.g. "MyFile is soGreat.txt" -> "MyFile is soGreat.txt My File so Great"
|
|
151
|
+
* Note: it doesn't split up titles that don't have a file extension.
|
|
152
|
+
*/
|
|
153
|
+
export function splitFilename(title) {
|
|
154
|
+
if (!title) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
const parts = title.split(".");
|
|
158
|
+
if (parts.pop()?.includes(" ")) {
|
|
159
|
+
// There isn't an extension, so don't treat it as a filename
|
|
160
|
+
return title;
|
|
161
|
+
}
|
|
162
|
+
// split up camelCase into "camel Case"
|
|
163
|
+
return [
|
|
164
|
+
title,
|
|
165
|
+
...parts.flatMap((part) => {
|
|
166
|
+
const words = part.split(" ");
|
|
167
|
+
const camelCaseWords = words.flatMap((word) => {
|
|
168
|
+
const pieces = word.split(/(?=[A-Z])/);
|
|
169
|
+
if (pieces.length === 1) {
|
|
170
|
+
// This will already be verbatim in the regular title parts
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
return pieces;
|
|
174
|
+
});
|
|
175
|
+
return camelCaseWords;
|
|
176
|
+
}),
|
|
177
|
+
].join(" ");
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=fileUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileUtils.js","sourceRoot":"","sources":["../../src/client/fileUtils.ts"],"names":[],"mappings":"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"}
|