Spaces:
Sleeping
Sleeping
feat: add admin only export feature
Browse files- package-lock.json +33 -1
- package.json +3 -1
- src/lib/server/api/index.ts +5 -1
- src/lib/server/api/routes/groups/misc.ts +132 -0
package-lock.json
CHANGED
|
@@ -85,6 +85,7 @@
|
|
| 85 |
"@types/parquetjs": "^0.10.3",
|
| 86 |
"@types/sbd": "^1.0.5",
|
| 87 |
"@types/uuid": "^9.0.8",
|
|
|
|
| 88 |
"@typescript-eslint/eslint-plugin": "^6.x",
|
| 89 |
"@typescript-eslint/parser": "^6.x",
|
| 90 |
"bson-objectid": "^2.0.4",
|
|
@@ -115,7 +116,8 @@
|
|
| 115 |
"unplugin-icons": "^0.16.1",
|
| 116 |
"vite": "^6.3.5",
|
| 117 |
"vite-node": "^3.0.9",
|
| 118 |
-
"vitest": "^3.1.4"
|
|
|
|
| 119 |
},
|
| 120 |
"optionalDependencies": {
|
| 121 |
"@anthropic-ai/sdk": "^0.32.1",
|
|
@@ -6804,6 +6806,16 @@
|
|
| 6804 |
"@types/webidl-conversions": "*"
|
| 6805 |
}
|
| 6806 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6807 |
"node_modules/@typescript-eslint/eslint-plugin": {
|
| 6808 |
"version": "6.21.0",
|
| 6809 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
|
|
@@ -18735,6 +18747,26 @@
|
|
| 18735 |
"node": ">=12"
|
| 18736 |
}
|
| 18737 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18738 |
"node_modules/yn": {
|
| 18739 |
"version": "3.1.1",
|
| 18740 |
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
|
|
|
| 85 |
"@types/parquetjs": "^0.10.3",
|
| 86 |
"@types/sbd": "^1.0.5",
|
| 87 |
"@types/uuid": "^9.0.8",
|
| 88 |
+
"@types/yazl": "^3.3.0",
|
| 89 |
"@typescript-eslint/eslint-plugin": "^6.x",
|
| 90 |
"@typescript-eslint/parser": "^6.x",
|
| 91 |
"bson-objectid": "^2.0.4",
|
|
|
|
| 116 |
"unplugin-icons": "^0.16.1",
|
| 117 |
"vite": "^6.3.5",
|
| 118 |
"vite-node": "^3.0.9",
|
| 119 |
+
"vitest": "^3.1.4",
|
| 120 |
+
"yazl": "^3.3.1"
|
| 121 |
},
|
| 122 |
"optionalDependencies": {
|
| 123 |
"@anthropic-ai/sdk": "^0.32.1",
|
|
|
|
| 6806 |
"@types/webidl-conversions": "*"
|
| 6807 |
}
|
| 6808 |
},
|
| 6809 |
+
"node_modules/@types/yazl": {
|
| 6810 |
+
"version": "3.3.0",
|
| 6811 |
+
"resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-3.3.0.tgz",
|
| 6812 |
+
"integrity": "sha512-mFL6lGkk2N5u5nIxpNV/K5LW3qVSbxhJrMxYGOOxZndWxMgCamr/iCsq/1t9kd8pEwhuNP91LC5qZm/qS9pOEw==",
|
| 6813 |
+
"dev": true,
|
| 6814 |
+
"license": "MIT",
|
| 6815 |
+
"dependencies": {
|
| 6816 |
+
"@types/node": "*"
|
| 6817 |
+
}
|
| 6818 |
+
},
|
| 6819 |
"node_modules/@typescript-eslint/eslint-plugin": {
|
| 6820 |
"version": "6.21.0",
|
| 6821 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
|
|
|
|
| 18747 |
"node": ">=12"
|
| 18748 |
}
|
| 18749 |
},
|
| 18750 |
+
"node_modules/yazl": {
|
| 18751 |
+
"version": "3.3.1",
|
| 18752 |
+
"resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz",
|
| 18753 |
+
"integrity": "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==",
|
| 18754 |
+
"dev": true,
|
| 18755 |
+
"license": "MIT",
|
| 18756 |
+
"dependencies": {
|
| 18757 |
+
"buffer-crc32": "^1.0.0"
|
| 18758 |
+
}
|
| 18759 |
+
},
|
| 18760 |
+
"node_modules/yazl/node_modules/buffer-crc32": {
|
| 18761 |
+
"version": "1.0.0",
|
| 18762 |
+
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
|
| 18763 |
+
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
|
| 18764 |
+
"dev": true,
|
| 18765 |
+
"license": "MIT",
|
| 18766 |
+
"engines": {
|
| 18767 |
+
"node": ">=8.0.0"
|
| 18768 |
+
}
|
| 18769 |
+
},
|
| 18770 |
"node_modules/yn": {
|
| 18771 |
"version": "3.1.1",
|
| 18772 |
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
package.json
CHANGED
|
@@ -41,6 +41,7 @@
|
|
| 41 |
"@types/parquetjs": "^0.10.3",
|
| 42 |
"@types/sbd": "^1.0.5",
|
| 43 |
"@types/uuid": "^9.0.8",
|
|
|
|
| 44 |
"@typescript-eslint/eslint-plugin": "^6.x",
|
| 45 |
"@typescript-eslint/parser": "^6.x",
|
| 46 |
"bson-objectid": "^2.0.4",
|
|
@@ -71,7 +72,8 @@
|
|
| 71 |
"unplugin-icons": "^0.16.1",
|
| 72 |
"vite": "^6.3.5",
|
| 73 |
"vite-node": "^3.0.9",
|
| 74 |
-
"vitest": "^3.1.4"
|
|
|
|
| 75 |
},
|
| 76 |
"type": "module",
|
| 77 |
"dependencies": {
|
|
|
|
| 41 |
"@types/parquetjs": "^0.10.3",
|
| 42 |
"@types/sbd": "^1.0.5",
|
| 43 |
"@types/uuid": "^9.0.8",
|
| 44 |
+
"@types/yazl": "^3.3.0",
|
| 45 |
"@typescript-eslint/eslint-plugin": "^6.x",
|
| 46 |
"@typescript-eslint/parser": "^6.x",
|
| 47 |
"bson-objectid": "^2.0.4",
|
|
|
|
| 72 |
"unplugin-icons": "^0.16.1",
|
| 73 |
"vite": "^6.3.5",
|
| 74 |
"vite-node": "^3.0.9",
|
| 75 |
+
"vitest": "^3.1.4",
|
| 76 |
+
"yazl": "^3.3.1"
|
| 77 |
},
|
| 78 |
"type": "module",
|
| 79 |
"dependencies": {
|
src/lib/server/api/index.ts
CHANGED
|
@@ -16,7 +16,11 @@ import superjson from "superjson";
|
|
| 16 |
const prefix = `${base}/api/v2` as unknown as "";
|
| 17 |
|
| 18 |
export const app = new Elysia({ prefix })
|
| 19 |
-
.mapResponse(({ response }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
return new Response(superjson.stringify(response), {
|
| 21 |
headers: {
|
| 22 |
"Content-Type": "application/json",
|
|
|
|
| 16 |
const prefix = `${base}/api/v2` as unknown as "";
|
| 17 |
|
| 18 |
export const app = new Elysia({ prefix })
|
| 19 |
+
.mapResponse(({ response, request }) => {
|
| 20 |
+
// Skip the /export endpoint
|
| 21 |
+
if (request.url.endsWith("/export")) {
|
| 22 |
+
return response as unknown as Response;
|
| 23 |
+
}
|
| 24 |
return new Response(superjson.stringify(response), {
|
| 25 |
headers: {
|
| 26 |
"Content-Type": "application/json",
|
src/lib/server/api/routes/groups/misc.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { collections } from "$lib/server/database";
|
|
| 5 |
import { authCondition } from "$lib/server/auth";
|
| 6 |
import { config } from "$lib/server/config";
|
| 7 |
import { Client } from "@gradio/client";
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
export interface FeatureFlags {
|
| 10 |
searchEnabled: boolean;
|
|
@@ -103,4 +106,133 @@ export const misc = new Elysia()
|
|
| 103 |
} catch (e) {
|
| 104 |
throw new Error("Error fetching space API. Is the name correct?");
|
| 105 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
});
|
|
|
|
| 5 |
import { authCondition } from "$lib/server/auth";
|
| 6 |
import { config } from "$lib/server/config";
|
| 7 |
import { Client } from "@gradio/client";
|
| 8 |
+
import yazl from "yazl";
|
| 9 |
+
import { downloadFile } from "$lib/server/files/downloadFile";
|
| 10 |
+
import mimeTypes from "mime-types";
|
| 11 |
|
| 12 |
export interface FeatureFlags {
|
| 13 |
searchEnabled: boolean;
|
|
|
|
| 106 |
} catch (e) {
|
| 107 |
throw new Error("Error fetching space API. Is the name correct?");
|
| 108 |
}
|
| 109 |
+
})
|
| 110 |
+
.get("/export", async ({ locals }) => {
|
| 111 |
+
if (!locals.user) {
|
| 112 |
+
throw new Error("Not logged in");
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
if (!locals.isAdmin) {
|
| 116 |
+
throw new Error("Not admin");
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
const zipfile = new yazl.ZipFile();
|
| 120 |
+
|
| 121 |
+
const promises = [
|
| 122 |
+
collections.conversations
|
| 123 |
+
.find({ ...authCondition(locals) })
|
| 124 |
+
.toArray()
|
| 125 |
+
.then(async (conversations) => {
|
| 126 |
+
const formattedConversations = await Promise.all(
|
| 127 |
+
conversations.map(async (conversation) => {
|
| 128 |
+
const hashes: string[] = [];
|
| 129 |
+
conversation.messages.forEach(async (message) => {
|
| 130 |
+
if (message.files) {
|
| 131 |
+
message.files.forEach((file) => {
|
| 132 |
+
hashes.push(file.value);
|
| 133 |
+
});
|
| 134 |
+
}
|
| 135 |
+
});
|
| 136 |
+
const files = await Promise.all(
|
| 137 |
+
hashes.map(async (hash) => {
|
| 138 |
+
const fileData = await downloadFile(hash, conversation._id);
|
| 139 |
+
return fileData;
|
| 140 |
+
})
|
| 141 |
+
);
|
| 142 |
+
|
| 143 |
+
const filenames: string[] = [];
|
| 144 |
+
files.forEach((file) => {
|
| 145 |
+
const extension = mimeTypes.extension(file.mime) || "bin";
|
| 146 |
+
const convId = conversation._id.toString();
|
| 147 |
+
const fileId = file.name.split("-")[1].slice(0, 8);
|
| 148 |
+
const fileName = `file-${convId}-${fileId}.${extension}`;
|
| 149 |
+
filenames.push(fileName);
|
| 150 |
+
zipfile.addBuffer(Buffer.from(file.value, "base64"), fileName);
|
| 151 |
+
});
|
| 152 |
+
|
| 153 |
+
return {
|
| 154 |
+
...conversation,
|
| 155 |
+
messages: conversation.messages.map((message) => {
|
| 156 |
+
return {
|
| 157 |
+
...message,
|
| 158 |
+
files: filenames,
|
| 159 |
+
updates: undefined,
|
| 160 |
+
};
|
| 161 |
+
}),
|
| 162 |
+
};
|
| 163 |
+
})
|
| 164 |
+
);
|
| 165 |
+
|
| 166 |
+
zipfile.addBuffer(
|
| 167 |
+
Buffer.from(JSON.stringify(formattedConversations, null, 2)),
|
| 168 |
+
"conversations.json"
|
| 169 |
+
);
|
| 170 |
+
}),
|
| 171 |
+
collections.assistants
|
| 172 |
+
.find({ createdById: locals.user._id })
|
| 173 |
+
.toArray()
|
| 174 |
+
.then(async (assistants) => {
|
| 175 |
+
const formattedAssistants = await Promise.all(
|
| 176 |
+
assistants.map(async (assistant) => {
|
| 177 |
+
if (assistant.avatar) {
|
| 178 |
+
const fileId = collections.bucket.find({ filename: assistant._id.toString() });
|
| 179 |
+
|
| 180 |
+
const content = await fileId.next().then(async (file) => {
|
| 181 |
+
if (!file?._id) return;
|
| 182 |
+
|
| 183 |
+
const fileStream = collections.bucket.openDownloadStream(file?._id);
|
| 184 |
+
|
| 185 |
+
const fileBuffer = await new Promise<Buffer>((resolve, reject) => {
|
| 186 |
+
const chunks: Uint8Array[] = [];
|
| 187 |
+
fileStream.on("data", (chunk) => chunks.push(chunk));
|
| 188 |
+
fileStream.on("error", reject);
|
| 189 |
+
fileStream.on("end", () => resolve(Buffer.concat(chunks)));
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
return fileBuffer;
|
| 193 |
+
});
|
| 194 |
+
|
| 195 |
+
if (!content) return;
|
| 196 |
+
|
| 197 |
+
zipfile.addBuffer(content, `avatar-${assistant._id.toString()}.jpg`);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
return {
|
| 201 |
+
_id: assistant._id.toString(),
|
| 202 |
+
name: assistant.name,
|
| 203 |
+
createdById: assistant.createdById.toString(),
|
| 204 |
+
createdByName: assistant.createdByName,
|
| 205 |
+
avatar: `avatar-${assistant._id.toString()}.jpg`,
|
| 206 |
+
modelId: assistant.modelId,
|
| 207 |
+
preprompt: assistant.preprompt,
|
| 208 |
+
description: assistant.description,
|
| 209 |
+
dynamicPrompt: assistant.dynamicPrompt,
|
| 210 |
+
exampleInputs: assistant.exampleInputs,
|
| 211 |
+
rag: assistant.rag,
|
| 212 |
+
tools: assistant.tools,
|
| 213 |
+
generateSettings: assistant.generateSettings,
|
| 214 |
+
createdAt: assistant.createdAt.toISOString(),
|
| 215 |
+
updatedAt: assistant.updatedAt.toISOString(),
|
| 216 |
+
};
|
| 217 |
+
})
|
| 218 |
+
);
|
| 219 |
+
|
| 220 |
+
zipfile.addBuffer(
|
| 221 |
+
Buffer.from(JSON.stringify(formattedAssistants, null, 2)),
|
| 222 |
+
"assistants.json"
|
| 223 |
+
);
|
| 224 |
+
}),
|
| 225 |
+
];
|
| 226 |
+
|
| 227 |
+
await Promise.all(promises);
|
| 228 |
+
|
| 229 |
+
zipfile.end();
|
| 230 |
+
|
| 231 |
+
// @ts-expect-error - zipfile.outputStream is not typed correctly
|
| 232 |
+
return new Response(zipfile.outputStream, {
|
| 233 |
+
headers: {
|
| 234 |
+
"Content-Type": "application/zip",
|
| 235 |
+
"Content-Disposition": 'attachment; filename="export.zip"',
|
| 236 |
+
},
|
| 237 |
+
});
|
| 238 |
});
|