@promptbook/cli 0.112.0-134 → 0.112.0-136
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/apps/agents-server/README.md +0 -1
- package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +2 -4
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +2 -4
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +246 -15
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +237 -4
- package/apps/agents-server/src/app/api/admin/update/log/route.ts +42 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +28 -9
- package/apps/agents-server/src/app/api/agents/export/route.ts +41 -0
- package/apps/agents-server/src/app/api/agents/import/route.ts +168 -0
- package/apps/agents-server/src/app/api/chat/route.ts +29 -7
- package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-streaming/route.ts +29 -0
- package/apps/agents-server/src/app/api/elevenlabs/tts/route.ts +60 -2
- package/apps/agents-server/src/app/api/federated-agents/route.ts +1 -1
- package/apps/agents-server/src/app/api/images/[filename]/route.ts +102 -0
- package/apps/agents-server/src/app/api/metadata/export/route.ts +34 -0
- package/apps/agents-server/src/app/api/metadata/import/route.ts +46 -0
- package/apps/agents-server/src/app/api/openai/v1/audio/transcriptions/route.ts +111 -2
- package/apps/agents-server/src/app/api/page-preview/check/route.ts +18 -0
- package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +15 -8
- package/apps/agents-server/src/app/api/team-agent-profile/route.ts +20 -0
- package/apps/agents-server/src/app/layout.tsx +1 -1
- package/apps/agents-server/src/components/AgentContextMenu/useAgentContextMenuItems.ts +27 -19
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +31 -1
- package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +60 -1
- package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +523 -0
- package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +16 -0
- package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
- package/apps/agents-server/src/database/metadataDefaults.ts +0 -21
- package/apps/agents-server/src/database/migrations/2026-06-2700-default-agent-avatar-visual-octopus3d4.sql +16 -0
- package/apps/agents-server/src/database/seedCoreAgents.ts +235 -0
- package/apps/agents-server/src/database/seedDefaultAgents.ts +32 -1
- package/apps/agents-server/src/search/createDefaultServerSearchProviders/createFederatedAgentsSearchProvider.ts +1 -1
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +2 -9
- package/apps/agents-server/src/utils/agentOwnership.ts +54 -5
- package/apps/agents-server/src/utils/agentsTransfer/createAgentsExportZipStream.ts +68 -0
- package/apps/agents-server/src/utils/agentsTransfer/importAgentsFromFiles.ts +852 -0
- package/apps/agents-server/src/utils/backup/createBooksBackupZipStream.ts +15 -2
- package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +46 -4
- package/apps/agents-server/src/utils/findAgentForCallerWriteAccess.ts +36 -0
- package/apps/agents-server/src/utils/getFederatedServers.ts +3 -74
- package/apps/agents-server/src/utils/getWellKnownAgentUrl.ts +10 -4
- package/apps/agents-server/src/utils/metadataConfigurationTransfer.ts +426 -0
- package/apps/agents-server/src/utils/paidApiRequestGuard.ts +241 -0
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +2 -5
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +162 -0
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +36 -8
- package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +11 -2
- package/apps/agents-server/src/utils/userChat/resolveUserChatProgressToolHighlights.ts +100 -0
- package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +4 -3
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +272 -27
- package/apps/agents-server/src/utils/validateApiKey.ts +7 -3
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +110 -0
- package/esm/index.es.js +795 -39
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/core.index.d.ts +0 -2
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/servers.ts +1 -16
- package/src/_packages/core.index.ts +0 -2
- package/src/avatars/types/AvatarVisualDefinition.ts +1 -0
- package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
- package/src/avatars/visuals/octopus3d4AvatarVisual.ts +1295 -0
- package/src/other/templates/getTemplatesPipelineCollection.ts +762 -883
- package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
- package/src/utils/validators/url/isValidAgentUrl.ts +5 -7
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -1
- package/umd/index.umd.js +795 -39
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/core.index.d.ts +0 -2
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/utils/defaultFederatedAgents/DefaultFederatedAgentsSyncOptions.ts +0 -9
- package/apps/agents-server/src/utils/defaultFederatedAgents/ensureDefaultFederatedAgentExists.ts +0 -277
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchCoreOrganizationPayload.ts +0 -39
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchFederatedAgentBook.ts +0 -43
- package/apps/agents-server/src/utils/defaultFederatedAgents/fetchWithDefaultFederatedAgentTimeout.ts +0 -28
- package/apps/agents-server/src/utils/defaultFederatedAgents/getDefaultFederatedAgentSyncPool.ts +0 -38
- package/apps/agents-server/src/utils/defaultFederatedAgents/loadActiveLocalAgentIdsByNormalizedName.ts +0 -41
- package/apps/agents-server/src/utils/defaultFederatedAgents/loadDefaultFederatedAgentSyncMetadata.ts +0 -76
- package/apps/agents-server/src/utils/defaultFederatedAgents/quoteIdentifier.ts +0 -11
- package/apps/agents-server/src/utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync.ts +0 -88
- package/apps/agents-server/src/utils/defaultFederatedAgents/selectDefaultFederatedAgentsFromOrganizationPayload.ts +0 -181
- package/apps/agents-server/src/utils/defaultFederatedAgents/synchronizeDefaultFederatedAgents.ts +0 -77
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import JSZip from 'jszip';
|
|
4
|
+
import { useCallback, useRef, useState, type ChangeEvent, type DragEvent, type RefObject } from 'react';
|
|
5
|
+
import { downloadBlob, parseFilenameFromContentDisposition } from '../../utils/download/browserFileDownload';
|
|
6
|
+
import { showAlert, showConfirm, showPrompt } from '../AsyncDialogs/asyncDialogs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Endpoint serving agents-only ZIP exports.
|
|
10
|
+
*/
|
|
11
|
+
const AGENTS_EXPORT_ENDPOINT = '/api/agents/export';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Endpoint accepting dropped agents book/ZIP imports.
|
|
15
|
+
*/
|
|
16
|
+
const AGENTS_IMPORT_ENDPOINT = '/api/agents/import';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fallback export filename when the response omits a filename.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_AGENTS_EXPORT_FILENAME = 'promptbook-agents-server.agents.zip';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Form field used for uploaded import files.
|
|
25
|
+
*/
|
|
26
|
+
const FILES_FORM_FIELD = 'files';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Form field used for the target folder id.
|
|
30
|
+
*/
|
|
31
|
+
const TARGET_FOLDER_ID_FORM_FIELD = 'targetFolderId';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Form field used for duplicate conflict handling.
|
|
35
|
+
*/
|
|
36
|
+
const CONFLICT_RESOLUTION_FORM_FIELD = 'conflictResolution';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* File input accept list for agents imports.
|
|
40
|
+
*/
|
|
41
|
+
export const AGENTS_IMPORT_FILE_INPUT_ACCEPT = '.book,.zip,application/zip,application/x-zip-compressed';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Maximum number of warning or conflict paths shown in dialogs.
|
|
45
|
+
*/
|
|
46
|
+
const MAX_DIALOG_PATH_COUNT = 6;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Successful import response shape.
|
|
50
|
+
*/
|
|
51
|
+
type AgentsImportSuccessPayload = {
|
|
52
|
+
readonly success: true;
|
|
53
|
+
readonly importedCount: number;
|
|
54
|
+
readonly skippedCount: number;
|
|
55
|
+
readonly ignoredFileCount: number;
|
|
56
|
+
readonly warnings?: ReadonlyArray<AgentsImportWarningPayload>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Duplicate conflict response shape.
|
|
61
|
+
*/
|
|
62
|
+
type AgentsImportConflictPayload = {
|
|
63
|
+
readonly success: false;
|
|
64
|
+
readonly code: 'agent_import_conflicts';
|
|
65
|
+
readonly message?: string;
|
|
66
|
+
readonly conflicts: ReadonlyArray<AgentsImportConflictPayloadItem>;
|
|
67
|
+
readonly warnings?: ReadonlyArray<AgentsImportWarningPayload>;
|
|
68
|
+
readonly ignoredFileCount?: number;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Minimal import error response shape.
|
|
73
|
+
*/
|
|
74
|
+
type AgentsImportErrorPayload = {
|
|
75
|
+
readonly error?: string;
|
|
76
|
+
readonly message?: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Warning payload returned by the agents import API.
|
|
81
|
+
*/
|
|
82
|
+
type AgentsImportWarningPayload = {
|
|
83
|
+
readonly path: string;
|
|
84
|
+
readonly message: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Duplicate conflict payload returned by the agents import API.
|
|
89
|
+
*/
|
|
90
|
+
type AgentsImportConflictPayloadItem = {
|
|
91
|
+
readonly agentName: string;
|
|
92
|
+
readonly path: string;
|
|
93
|
+
readonly existingDifferentBookCount: number;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Duplicate conflict handling accepted by the import API.
|
|
98
|
+
*/
|
|
99
|
+
type AgentsImportConflictResolution = 'ASK' | 'SKIP' | 'DUPLICATE';
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Hook options for Agents list import/export behavior.
|
|
103
|
+
*/
|
|
104
|
+
type UseAgentsListImportExportStateOptions = {
|
|
105
|
+
/**
|
|
106
|
+
* Current folder selected in the Agents list.
|
|
107
|
+
*/
|
|
108
|
+
readonly currentFolderId: number | null;
|
|
109
|
+
/**
|
|
110
|
+
* Refreshes the organization snapshot after a successful mutation.
|
|
111
|
+
*/
|
|
112
|
+
readonly synchronizeAfterMutation: (mutationName: string) => void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Hook state and handlers for Agents list import/export behavior.
|
|
117
|
+
*/
|
|
118
|
+
type UseAgentsListImportExportStateResult = {
|
|
119
|
+
readonly importFileInputRef: RefObject<HTMLInputElement | null>;
|
|
120
|
+
readonly handleAgentsExport: () => Promise<void>;
|
|
121
|
+
readonly handleAgentsImportClick: () => void;
|
|
122
|
+
readonly handleAgentsImportFileChange: (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
123
|
+
readonly handleAgentsFileDragEnter: (event: DragEvent<HTMLElement>) => void;
|
|
124
|
+
readonly handleAgentsFileDragLeave: (event: DragEvent<HTMLElement>) => void;
|
|
125
|
+
readonly handleAgentsFileDragOver: (event: DragEvent<HTMLElement>) => void;
|
|
126
|
+
readonly handleAgentsFileDrop: (event: DragEvent<HTMLElement>) => Promise<void>;
|
|
127
|
+
readonly isAgentsExporting: boolean;
|
|
128
|
+
readonly isAgentsImportDragActive: boolean;
|
|
129
|
+
readonly isAgentsImporting: boolean;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Owns agents import/export browser interactions for `AgentsList`.
|
|
134
|
+
*
|
|
135
|
+
* @param options - Current folder and synchronization callback.
|
|
136
|
+
* @returns Import/export handlers and state.
|
|
137
|
+
*
|
|
138
|
+
* @private function of AgentsList
|
|
139
|
+
*/
|
|
140
|
+
export function useAgentsListImportExportState({
|
|
141
|
+
currentFolderId,
|
|
142
|
+
synchronizeAfterMutation,
|
|
143
|
+
}: UseAgentsListImportExportStateOptions): UseAgentsListImportExportStateResult {
|
|
144
|
+
const importFileInputRef = useRef<HTMLInputElement | null>(null);
|
|
145
|
+
const dragDepthRef = useRef(0);
|
|
146
|
+
const [isAgentsExporting, setIsAgentsExporting] = useState(false);
|
|
147
|
+
const [isAgentsImporting, setIsAgentsImporting] = useState(false);
|
|
148
|
+
const [isAgentsImportDragActive, setIsAgentsImportDragActive] = useState(false);
|
|
149
|
+
|
|
150
|
+
const handleAgentsExport = useCallback(async () => {
|
|
151
|
+
if (isAgentsExporting) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setIsAgentsExporting(true);
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(AGENTS_EXPORT_ENDPOINT, { method: 'GET' });
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(await resolveAgentsTransferErrorMessage(response, 'Failed to export agents.'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const filename =
|
|
164
|
+
parseFilenameFromContentDisposition(response.headers.get('Content-Disposition')) ||
|
|
165
|
+
DEFAULT_AGENTS_EXPORT_FILENAME;
|
|
166
|
+
|
|
167
|
+
downloadBlob(await response.blob(), filename);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
await showAlert({
|
|
170
|
+
title: 'Export failed',
|
|
171
|
+
message: error instanceof Error ? error.message : 'Failed to export agents.',
|
|
172
|
+
}).catch(() => undefined);
|
|
173
|
+
} finally {
|
|
174
|
+
setIsAgentsExporting(false);
|
|
175
|
+
}
|
|
176
|
+
}, [isAgentsExporting]);
|
|
177
|
+
|
|
178
|
+
const importAgentsFiles = useCallback(
|
|
179
|
+
async (files: ReadonlyArray<File>) => {
|
|
180
|
+
if (isAgentsImporting || files.length === 0) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const isConfirmed = await confirmNonBookZipEntries(files);
|
|
185
|
+
if (!isConfirmed) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
setIsAgentsImporting(true);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const initialResponse = await uploadAgentsImportFiles({
|
|
193
|
+
files,
|
|
194
|
+
targetFolderId: currentFolderId,
|
|
195
|
+
conflictResolution: 'ASK',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (initialResponse.status === 409) {
|
|
199
|
+
const conflictPayload = (await initialResponse.json()) as AgentsImportConflictPayload;
|
|
200
|
+
const conflictResolution = await promptAgentsImportConflictResolution(conflictPayload.conflicts);
|
|
201
|
+
if (!conflictResolution) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const resolvedResponse = await uploadAgentsImportFiles({
|
|
206
|
+
files,
|
|
207
|
+
targetFolderId: currentFolderId,
|
|
208
|
+
conflictResolution,
|
|
209
|
+
});
|
|
210
|
+
await handleAgentsImportResponse(resolvedResponse, synchronizeAfterMutation);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await handleAgentsImportResponse(initialResponse, synchronizeAfterMutation);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
await showAlert({
|
|
217
|
+
title: 'Import failed',
|
|
218
|
+
message: error instanceof Error ? error.message : 'Failed to import agents.',
|
|
219
|
+
}).catch(() => undefined);
|
|
220
|
+
} finally {
|
|
221
|
+
setIsAgentsImporting(false);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
[currentFolderId, isAgentsImporting, synchronizeAfterMutation],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const handleAgentsImportClick = useCallback(() => {
|
|
228
|
+
if (isAgentsImporting) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
importFileInputRef.current?.click();
|
|
233
|
+
}, [isAgentsImporting]);
|
|
234
|
+
|
|
235
|
+
const handleAgentsImportFileChange = useCallback(
|
|
236
|
+
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
237
|
+
const files = Array.from(event.target.files || []);
|
|
238
|
+
await importAgentsFiles(files);
|
|
239
|
+
|
|
240
|
+
if (importFileInputRef.current) {
|
|
241
|
+
importFileInputRef.current.value = '';
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
[importAgentsFiles],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const handleAgentsFileDragEnter = useCallback((event: DragEvent<HTMLElement>) => {
|
|
248
|
+
if (!isFileDragEvent(event)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
dragDepthRef.current += 1;
|
|
254
|
+
setIsAgentsImportDragActive(true);
|
|
255
|
+
}, []);
|
|
256
|
+
|
|
257
|
+
const handleAgentsFileDragLeave = useCallback((event: DragEvent<HTMLElement>) => {
|
|
258
|
+
if (!isFileDragEvent(event)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
|
|
263
|
+
if (dragDepthRef.current === 0) {
|
|
264
|
+
setIsAgentsImportDragActive(false);
|
|
265
|
+
}
|
|
266
|
+
}, []);
|
|
267
|
+
|
|
268
|
+
const handleAgentsFileDragOver = useCallback((event: DragEvent<HTMLElement>) => {
|
|
269
|
+
if (!isFileDragEvent(event)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
277
|
+
const handleAgentsFileDrop = useCallback(
|
|
278
|
+
async (event: DragEvent<HTMLElement>) => {
|
|
279
|
+
if (!isFileDragEvent(event)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
dragDepthRef.current = 0;
|
|
285
|
+
setIsAgentsImportDragActive(false);
|
|
286
|
+
|
|
287
|
+
await importAgentsFiles(Array.from(event.dataTransfer.files || []));
|
|
288
|
+
},
|
|
289
|
+
[importAgentsFiles],
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
importFileInputRef,
|
|
294
|
+
handleAgentsExport,
|
|
295
|
+
handleAgentsImportClick,
|
|
296
|
+
handleAgentsImportFileChange,
|
|
297
|
+
handleAgentsFileDragEnter,
|
|
298
|
+
handleAgentsFileDragLeave,
|
|
299
|
+
handleAgentsFileDragOver,
|
|
300
|
+
handleAgentsFileDrop,
|
|
301
|
+
isAgentsExporting,
|
|
302
|
+
isAgentsImportDragActive,
|
|
303
|
+
isAgentsImporting,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Uploads selected files to the agents import endpoint.
|
|
309
|
+
*
|
|
310
|
+
* @param options - Files, target folder, and duplicate behavior.
|
|
311
|
+
* @returns Fetch response.
|
|
312
|
+
*/
|
|
313
|
+
async function uploadAgentsImportFiles(options: {
|
|
314
|
+
readonly files: ReadonlyArray<File>;
|
|
315
|
+
readonly targetFolderId: number | null;
|
|
316
|
+
readonly conflictResolution: AgentsImportConflictResolution;
|
|
317
|
+
}): Promise<Response> {
|
|
318
|
+
const formData = new FormData();
|
|
319
|
+
|
|
320
|
+
for (const file of options.files) {
|
|
321
|
+
formData.append(FILES_FORM_FIELD, file);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
formData.append(TARGET_FOLDER_ID_FORM_FIELD, options.targetFolderId === null ? 'null' : String(options.targetFolderId));
|
|
325
|
+
formData.append(CONFLICT_RESOLUTION_FORM_FIELD, options.conflictResolution);
|
|
326
|
+
|
|
327
|
+
return fetch(AGENTS_IMPORT_ENDPOINT, {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
body: formData,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Handles a final agents import response.
|
|
335
|
+
*
|
|
336
|
+
* @param response - Final import response.
|
|
337
|
+
* @param synchronizeAfterMutation - Organization snapshot refresh callback.
|
|
338
|
+
*/
|
|
339
|
+
async function handleAgentsImportResponse(
|
|
340
|
+
response: Response,
|
|
341
|
+
synchronizeAfterMutation: (mutationName: string) => void,
|
|
342
|
+
): Promise<void> {
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
throw new Error(await resolveAgentsTransferErrorMessage(response, 'Failed to import agents.'));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const payload = (await response.json()) as AgentsImportSuccessPayload;
|
|
348
|
+
if (payload.importedCount > 0) {
|
|
349
|
+
synchronizeAfterMutation('import-agents');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await showAlert({
|
|
353
|
+
title: 'Import complete',
|
|
354
|
+
message: createAgentsImportSuccessMessage(payload),
|
|
355
|
+
}).catch(() => undefined);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Creates a concise import success message.
|
|
360
|
+
*
|
|
361
|
+
* @param payload - Successful import payload.
|
|
362
|
+
* @returns Dialog message.
|
|
363
|
+
*/
|
|
364
|
+
function createAgentsImportSuccessMessage(payload: AgentsImportSuccessPayload): string {
|
|
365
|
+
const parts = [`Imported ${payload.importedCount} agents.`];
|
|
366
|
+
|
|
367
|
+
if (payload.skippedCount > 0) {
|
|
368
|
+
parts.push(`Skipped ${payload.skippedCount} duplicates.`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (payload.ignoredFileCount > 0) {
|
|
372
|
+
parts.push(`Ignored ${payload.ignoredFileCount} non-book files.`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (payload.warnings && payload.warnings.length > 0) {
|
|
376
|
+
parts.push(createWarningListMessage(payload.warnings.map((warning) => warning.path)));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return parts.join('\n\n');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Prompts the user for duplicate conflict behavior.
|
|
384
|
+
*
|
|
385
|
+
* @param conflicts - Duplicate conflicts returned by the import API.
|
|
386
|
+
* @returns Selected conflict resolution, or `null` when cancelled.
|
|
387
|
+
*/
|
|
388
|
+
async function promptAgentsImportConflictResolution(
|
|
389
|
+
conflicts: ReadonlyArray<AgentsImportConflictPayloadItem>,
|
|
390
|
+
): Promise<Exclude<AgentsImportConflictResolution, 'ASK'> | null> {
|
|
391
|
+
const decision = await showPrompt({
|
|
392
|
+
title: 'Duplicate agents',
|
|
393
|
+
message: [
|
|
394
|
+
'Some dropped books have the same agent name as existing agents but different book source.',
|
|
395
|
+
'Type `duplicate` to import them as additional agents, or `skip` to skip the conflicting books.',
|
|
396
|
+
createWarningListMessage(conflicts.map((conflict) => `${conflict.agentName}: ${conflict.path}`)),
|
|
397
|
+
].join('\n\n'),
|
|
398
|
+
inputLabel: 'Decision',
|
|
399
|
+
defaultValue: 'skip',
|
|
400
|
+
placeholder: 'duplicate or skip',
|
|
401
|
+
confirmLabel: 'Continue',
|
|
402
|
+
cancelLabel: 'Cancel',
|
|
403
|
+
}).catch(() => '');
|
|
404
|
+
|
|
405
|
+
const normalizedDecision = decision.trim().toLowerCase();
|
|
406
|
+
if (normalizedDecision === 'duplicate') {
|
|
407
|
+
return 'DUPLICATE';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (normalizedDecision === 'skip') {
|
|
411
|
+
return 'SKIP';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (normalizedDecision) {
|
|
415
|
+
await showAlert({
|
|
416
|
+
title: 'Import cancelled',
|
|
417
|
+
message: 'Duplicate decision must be `duplicate` or `skip`.',
|
|
418
|
+
}).catch(() => undefined);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Warns when ZIP files contain non-book entries.
|
|
426
|
+
*
|
|
427
|
+
* @param files - Selected files.
|
|
428
|
+
* @returns Whether import should continue.
|
|
429
|
+
*/
|
|
430
|
+
async function confirmNonBookZipEntries(files: ReadonlyArray<File>): Promise<boolean> {
|
|
431
|
+
const nonBookZipEntryPaths = await collectNonBookZipEntryPaths(files);
|
|
432
|
+
if (nonBookZipEntryPaths.length === 0) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return showConfirm({
|
|
437
|
+
title: 'Import agents',
|
|
438
|
+
message: [
|
|
439
|
+
'The ZIP archive contains files that are not `.book` files. They will be ignored.',
|
|
440
|
+
createWarningListMessage(nonBookZipEntryPaths),
|
|
441
|
+
].join('\n\n'),
|
|
442
|
+
confirmLabel: 'Import agents',
|
|
443
|
+
cancelLabel: 'Cancel',
|
|
444
|
+
}).catch(() => false);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Collects non-book file paths from selected ZIP archives.
|
|
449
|
+
*
|
|
450
|
+
* @param files - Selected files.
|
|
451
|
+
* @returns Non-book ZIP entry paths.
|
|
452
|
+
*/
|
|
453
|
+
async function collectNonBookZipEntryPaths(files: ReadonlyArray<File>): Promise<string[]> {
|
|
454
|
+
const nonBookZipEntryPaths: string[] = [];
|
|
455
|
+
|
|
456
|
+
for (const file of files) {
|
|
457
|
+
if (!file.name.toLowerCase().endsWith('.zip')) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
const zip = await JSZip.loadAsync(await file.arrayBuffer());
|
|
463
|
+
for (const zipEntry of Object.values(zip.files)) {
|
|
464
|
+
if (!zipEntry.dir && !zipEntry.name.toLowerCase().endsWith('.book')) {
|
|
465
|
+
nonBookZipEntryPaths.push(`${file.name}/${zipEntry.name}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} catch {
|
|
469
|
+
// Let the server return the authoritative ZIP parse error during import.
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return nonBookZipEntryPaths;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Creates a shortened path list for dialogs.
|
|
478
|
+
*
|
|
479
|
+
* @param paths - File paths.
|
|
480
|
+
* @returns Human-readable path list.
|
|
481
|
+
*/
|
|
482
|
+
function createWarningListMessage(paths: ReadonlyArray<string>): string {
|
|
483
|
+
const visiblePaths = paths.slice(0, MAX_DIALOG_PATH_COUNT);
|
|
484
|
+
const remainingCount = paths.length - visiblePaths.length;
|
|
485
|
+
const visibleMessage = visiblePaths.map((path) => `- ${path}`).join('\n');
|
|
486
|
+
|
|
487
|
+
if (remainingCount <= 0) {
|
|
488
|
+
return visibleMessage;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return `${visibleMessage}\n- and ${remainingCount} more`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Reads a user-facing transfer error message from a failed response.
|
|
496
|
+
*
|
|
497
|
+
* @param response - Failed response.
|
|
498
|
+
* @param fallbackMessage - Fallback message.
|
|
499
|
+
* @returns User-facing message.
|
|
500
|
+
*/
|
|
501
|
+
async function resolveAgentsTransferErrorMessage(response: Response, fallbackMessage: string): Promise<string> {
|
|
502
|
+
try {
|
|
503
|
+
const payload = (await response.json()) as AgentsImportErrorPayload;
|
|
504
|
+
const message = payload.message || payload.error;
|
|
505
|
+
if (message && message.trim().length > 0) {
|
|
506
|
+
return message.trim();
|
|
507
|
+
}
|
|
508
|
+
} catch {
|
|
509
|
+
// Keep fallback when the response body is not JSON.
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return fallbackMessage;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Checks whether a drag event contains files.
|
|
517
|
+
*
|
|
518
|
+
* @param event - Drag event.
|
|
519
|
+
* @returns `true` when files are being dragged.
|
|
520
|
+
*/
|
|
521
|
+
function isFileDragEvent(event: DragEvent<HTMLElement>): boolean {
|
|
522
|
+
return Array.from(event.dataTransfer.types || []).includes('Files');
|
|
523
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './useAgentsListDragState';
|
|
16
16
|
import { useAgentsListFolderPathRecovery } from './useAgentsListFolderPathRecovery';
|
|
17
17
|
import { useAgentsListFolderState } from './useAgentsListFolderState';
|
|
18
|
+
import { useAgentsListImportExportState } from './useAgentsListImportExportState';
|
|
18
19
|
import { useAgentsListNavigationState } from './useAgentsListNavigationState';
|
|
19
20
|
import { useAgentsListOrganizationActions } from './useAgentsListOrganizationActions';
|
|
20
21
|
import { useAgentsListOverlayDetailsState } from './useAgentsListOverlayDetailsState';
|
|
@@ -166,6 +167,10 @@ export function useAgentsListState(props: UseAgentsListStateProps) {
|
|
|
166
167
|
setFolders,
|
|
167
168
|
synchronizeAfterMutation,
|
|
168
169
|
});
|
|
170
|
+
const importExportState = useAgentsListImportExportState({
|
|
171
|
+
currentFolderId,
|
|
172
|
+
synchronizeAfterMutation,
|
|
173
|
+
});
|
|
169
174
|
const agentState = useAgentsListAgentState({
|
|
170
175
|
agents,
|
|
171
176
|
formatText,
|
|
@@ -255,6 +260,13 @@ export function useAgentsListState(props: UseAgentsListStateProps) {
|
|
|
255
260
|
handleDragEnd: dragState.handleDragEnd,
|
|
256
261
|
handleDragOver: dragState.handleDragOver,
|
|
257
262
|
handleDragStart: dragState.handleDragStart,
|
|
263
|
+
handleAgentsExport: importExportState.handleAgentsExport,
|
|
264
|
+
handleAgentsFileDragEnter: importExportState.handleAgentsFileDragEnter,
|
|
265
|
+
handleAgentsFileDragLeave: importExportState.handleAgentsFileDragLeave,
|
|
266
|
+
handleAgentsFileDragOver: importExportState.handleAgentsFileDragOver,
|
|
267
|
+
handleAgentsFileDrop: importExportState.handleAgentsFileDrop,
|
|
268
|
+
handleAgentsImportClick: importExportState.handleAgentsImportClick,
|
|
269
|
+
handleAgentsImportFileChange: importExportState.handleAgentsImportFileChange,
|
|
258
270
|
handleFolderContextMenu: overlayState.handleFolderContextMenu,
|
|
259
271
|
handleRenameFolder: folderState.handleRenameFolder,
|
|
260
272
|
handleRequestAgentVisibilityChange: agentState.handleRequestAgentVisibilityChange,
|
|
@@ -262,6 +274,10 @@ export function useAgentsListState(props: UseAgentsListStateProps) {
|
|
|
262
274
|
handleShowQrCode: overlayState.handleShowQrCode,
|
|
263
275
|
handleSubmitFolderEdit: folderState.handleSubmitFolderEdit,
|
|
264
276
|
headingTitle,
|
|
277
|
+
importFileInputRef: importExportState.importFileInputRef,
|
|
278
|
+
isAgentsExporting: importExportState.isAgentsExporting,
|
|
279
|
+
isAgentsImportDragActive: importExportState.isAgentsImportDragActive,
|
|
280
|
+
isAgentsImporting: importExportState.isAgentsImporting,
|
|
265
281
|
isFolderEditSubmitting: folderState.isFolderEditSubmitting,
|
|
266
282
|
navigateToFolder,
|
|
267
283
|
mazeAgents,
|
|
@@ -53,7 +53,7 @@ export const DEFAULT_AGENT_AVATAR_VISUAL_METADATA_VALUES = DEFAULT_AGENT_AVATAR_
|
|
|
53
53
|
export const DEFAULT_AGENT_AVATAR_VISUAL_METADATA_VALUE =
|
|
54
54
|
DEFAULT_AGENT_AVATAR_VISUAL_METADATA_OPTIONS.find(
|
|
55
55
|
({ visualId }) => visualId === SHARED_DEFAULT_AGENT_AVATAR_VISUAL_ID,
|
|
56
|
-
)?.metadataValue || '
|
|
56
|
+
)?.metadataValue || 'OCTOPUS3D4';
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Resolves one raw metadata value to a supported built-in avatar visual id.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { spaceTrim } from 'spacetrim';
|
|
2
|
-
import { CORE_AGENTS_SERVER } from '../../../../servers';
|
|
3
1
|
import { DEFAULT_THINKING_MESSAGES } from '../../../../src/utils/DEFAULT_THINKING_MESSAGES';
|
|
4
2
|
import { ANALYTICS_METADATA_KEYS, getAnalyticsMetadataDefinition } from '../constants/analyticsMetadata';
|
|
5
3
|
import {
|
|
@@ -263,25 +261,6 @@ export const metadataDefaults: ReadonlyArray<MetadataDefinition> = [
|
|
|
263
261
|
note: 'Comma separated list of allowed IPs or CIDR ranges. If set, only clients from these IPs are allowed to access the server.',
|
|
264
262
|
type: 'IP_RANGE',
|
|
265
263
|
},
|
|
266
|
-
{
|
|
267
|
-
key: 'CORE_SERVER',
|
|
268
|
-
value: CORE_AGENTS_SERVER.url,
|
|
269
|
-
note: spaceTrim(`
|
|
270
|
-
Core Promptbook server URL used for agents that are used for common tasks, these agents are called well known agents:
|
|
271
|
-
|
|
272
|
-
On the core server, the following well known agents should be hosted:
|
|
273
|
-
- \`adam\`: The default ancestor agent for new agents
|
|
274
|
-
- \`teacher\`: Agent that knows book syntax and can help with self-learning
|
|
275
|
-
|
|
276
|
-
`),
|
|
277
|
-
type: 'TEXT',
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
key: 'IS_CORE_SERVER_HIDDEN',
|
|
281
|
-
value: 'false',
|
|
282
|
-
note: 'Hide the core federated server from homepage, navbar, search, and footer links while keeping it available for references when true.',
|
|
283
|
-
type: 'BOOLEAN',
|
|
284
|
-
},
|
|
285
264
|
// <- TODO: [🆎] Allow to set well-known agent names via Metadata
|
|
286
265
|
|
|
287
266
|
{
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
|
|
2
|
+
SELECT 'DEFAULT_AGENT_AVATAR_VISUAL',
|
|
3
|
+
'OCTOPUS3D4',
|
|
4
|
+
'Default built-in avatar visual used for agents without `META IMAGE` or `META AVATAR`. Allowed values: PIXEL_ART, OCTOPUS, OCTOPUS2, OCTOPUS3, OCTOPUS3D, OCTOPUS3D2, OCTOPUS3D3, OCTOPUS3D4, ASCII_OCTOPUS, MINECRAFT, MINECRAFT2, FRACTAL, ORB.',
|
|
5
|
+
NOW(),
|
|
6
|
+
NOW()
|
|
7
|
+
WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'DEFAULT_AGENT_AVATAR_VISUAL');
|
|
8
|
+
|
|
9
|
+
UPDATE "prefix_Metadata"
|
|
10
|
+
SET "value" = CASE
|
|
11
|
+
WHEN UPPER(COALESCE("value", '')) = 'OCTOPUS3D3' THEN 'OCTOPUS3D4'
|
|
12
|
+
ELSE "value"
|
|
13
|
+
END,
|
|
14
|
+
"note" = 'Default built-in avatar visual used for agents without `META IMAGE` or `META AVATAR`. Allowed values: PIXEL_ART, OCTOPUS, OCTOPUS2, OCTOPUS3, OCTOPUS3D, OCTOPUS3D2, OCTOPUS3D3, OCTOPUS3D4, ASCII_OCTOPUS, MINECRAFT, MINECRAFT2, FRACTAL, ORB.',
|
|
15
|
+
"updatedAt" = NOW()
|
|
16
|
+
WHERE "key" = 'DEFAULT_AGENT_AVATAR_VISUAL';
|