@@ -5,6 +5,9 @@ import { collections } from "$lib/server/database";
5
5
import { authCondition } from "$lib/server/auth" ;
6
6
import { config } from "$lib/server/config" ;
7
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" ;
8
11
9
12
export interface FeatureFlags {
10
13
searchEnabled : boolean ;
@@ -103,4 +106,133 @@ export const misc = new Elysia()
103
106
} catch ( e ) {
104
107
throw new Error ( "Error fetching space API. Is the name correct?" ) ;
105
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
+ } ) ;
106
238
} ) ;
0 commit comments