@promptbook/cli 0.104.0-1 → 0.104.0-2
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/next.config.ts +2 -2
- package/apps/agents-server/package.json +6 -1
- package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
- package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +50 -0
- package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +4 -3
- package/apps/agents-server/src/app/actions.ts +17 -5
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +15 -11
- package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -7
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +32 -2
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +2 -0
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +18 -0
- package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
- package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +17 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +1 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +1 -1
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +66 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +211 -0
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +1 -1
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +12 -6
- package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +87 -0
- package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +19 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +42 -0
- package/apps/agents-server/src/app/api/agents/route.ts +29 -4
- package/apps/agents-server/src/app/api/docs/book.md/route.ts +58 -0
- package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
- package/apps/agents-server/src/app/api/images/[filename]/route.ts +107 -0
- package/apps/agents-server/src/app/api/upload/route.ts +119 -45
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
- package/apps/agents-server/src/app/docs/page.tsx +12 -12
- package/apps/agents-server/src/app/globals.css +140 -33
- package/apps/agents-server/src/app/layout.tsx +27 -22
- package/apps/agents-server/src/app/page.tsx +50 -4
- package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
- package/apps/agents-server/src/app/recycle-bin/page.tsx +25 -41
- package/apps/agents-server/src/app/sitemap.xml/route.ts +6 -3
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +6 -97
- package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
- package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
- package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
- package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
- package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
- package/apps/agents-server/src/components/Header/Header.tsx +79 -35
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +85 -20
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +72 -12
- package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +50 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
- package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
- package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
- package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
- package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
- package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
- package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
- package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
- package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
- package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
- package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
- package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
- package/apps/agents-server/src/database/schema.ts +109 -0
- package/apps/agents-server/src/generated/reservedPaths.ts +27 -0
- package/apps/agents-server/src/middleware.ts +7 -20
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +6 -1
- package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
- package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
- package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
- package/apps/agents-server/src/utils/getUserIdFromRequest.ts +33 -0
- package/apps/agents-server/src/utils/handleChatCompletion.ts +60 -4
- package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +21 -0
- package/apps/agents-server/src/utils/validateApiKey.ts +2 -1
- package/esm/index.es.js +140 -27
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/types.index.d.ts +6 -2
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
- package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
- package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +2 -2
- package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +13 -7
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +6 -0
- package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
- package/esm/typings/src/commitments/index.d.ts +2 -1
- package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
- package/esm/typings/src/types/typeAliases.d.ts +12 -0
- package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
- package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
- package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
- package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
- package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +146 -33
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/package-lock.json +0 -27
- package/apps/agents-server/public/fonts/download-font.js +0 -22
- package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
|
@@ -2,8 +2,8 @@ import type { NextConfig } from 'next';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
4
|
const nextConfig: NextConfig = {
|
|
5
|
-
output: 'standalone',
|
|
6
|
-
// <- TODO: [🐱🚀][🧠] How to propperly build Next.js app
|
|
5
|
+
// output: 'standalone',
|
|
6
|
+
// <- TODO: [🐱🚀][🧠] How to propperly build Next.js app, for both Vercel and Doceker?
|
|
7
7
|
|
|
8
8
|
experimental: {
|
|
9
9
|
externalDir: true,
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptbook-agents-server",
|
|
3
3
|
"scripts": {
|
|
4
|
-
"migrate-database": "npx tsx src/database/migrate.ts",
|
|
4
|
+
"migrate-database": "npx tsx ./src/database/migrate.ts",
|
|
5
|
+
"generate-reserved-paths": "ts-node ./scripts/generate-reserved-paths/generate-reserved-paths.ts",
|
|
6
|
+
"prebuild": "npm run generate-reserved-paths",
|
|
7
|
+
"--postbuild": "echo {} > .next/export-detail.json && mkdir -p .next/export && echo \"Not found !!!!\" > .next/export/404.html && echo \"Server error !!!!\" > .next/export/500.html",
|
|
8
|
+
"--0": " <- !!!!",
|
|
9
|
+
"predev": "npm run generate-reserved-paths",
|
|
5
10
|
"dev": "(npx kill-port 4440 || true) && next dev -p 4440",
|
|
6
11
|
"test-build": "npm run build",
|
|
7
12
|
"build": "(npx kill-port 4440 || true) && next build",
|
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { GENERATOR_WARNING } from '../../../../src/config';
|
|
4
|
+
|
|
5
|
+
const appDir = path.join(__dirname, '..', '..', 'src', 'app');
|
|
6
|
+
const outputFile = path.join(__dirname, '..', '..', 'src', 'generated', 'reservedPaths.ts');
|
|
7
|
+
|
|
8
|
+
// Read the app directory
|
|
9
|
+
const entries = fs.readdirSync(appDir, { withFileTypes: true });
|
|
10
|
+
|
|
11
|
+
// Filter to get only directories (excluding dynamic routes like [agentName])
|
|
12
|
+
const reservedPaths = entries
|
|
13
|
+
.filter((entry) => entry.isDirectory())
|
|
14
|
+
.map((entry) => entry.name)
|
|
15
|
+
.filter((name) => !name.startsWith('[') && !name.endsWith(']'));
|
|
16
|
+
|
|
17
|
+
// Add additional paths that are not folders but should be reserved
|
|
18
|
+
const additionalPaths = [
|
|
19
|
+
'_next', // Next.js internal paths
|
|
20
|
+
'manifest.webmanifest', // PWA manifest
|
|
21
|
+
'sw.js', // Service Worker
|
|
22
|
+
'favicon.ico', // Favicon
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const allReservedPaths = [...new Set([...reservedPaths, ...additionalPaths])].sort();
|
|
26
|
+
|
|
27
|
+
// Generate the TypeScript file
|
|
28
|
+
const content = `/**
|
|
29
|
+
* Reserved paths that should not be treated as agent names.
|
|
30
|
+
* This file is auto-generated by scripts/generate-reserved-paths.js
|
|
31
|
+
*
|
|
32
|
+
* ${GENERATOR_WARNING}
|
|
33
|
+
*
|
|
34
|
+
* @see /apps/agents-server/src/app - source directory
|
|
35
|
+
* @see /apps/agents-server/src/middleware.ts - where this is used
|
|
36
|
+
*/
|
|
37
|
+
export const RESERVED_PATHS: readonly string[] = ${JSON.stringify(allReservedPaths, null, 4)} as const;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
// Ensure the generated directory exists
|
|
41
|
+
const generatedDir = path.dirname(outputFile);
|
|
42
|
+
if (!fs.existsSync(generatedDir)) {
|
|
43
|
+
fs.mkdirSync(generatedDir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Write the file
|
|
47
|
+
fs.writeFileSync(outputFile, content, 'utf-8');
|
|
48
|
+
|
|
49
|
+
console.log(`Generated ${outputFile} with ${allReservedPaths.length} reserved paths:`);
|
|
50
|
+
console.log(allReservedPaths.join(', '));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"downlevelIteration": true,
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"noImplicitReturns": true,
|
|
10
|
+
"noImplicitThis": true,
|
|
11
|
+
"noImplicitAny": true,
|
|
12
|
+
"strictNullChecks": true,
|
|
13
|
+
"experimentalDecorators": true,
|
|
14
|
+
"noUnusedLocals": false,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"esModuleInterop": true
|
|
17
|
+
},
|
|
18
|
+
"exclude": ["node_modules"]
|
|
19
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
'use client';
|
|
2
3
|
|
|
3
4
|
import { useRouter } from 'next/navigation';
|
|
@@ -11,10 +12,10 @@ export function AddAgentButton() {
|
|
|
11
12
|
|
|
12
13
|
const handleAddAgent = async () => {
|
|
13
14
|
setIsLoading(true);
|
|
14
|
-
const agentName = await $createAgentAction();
|
|
15
|
+
const { agentName, permanentId } = await $createAgentAction();
|
|
15
16
|
// TODO: Add proper error handling and UI feedback
|
|
16
|
-
if (
|
|
17
|
-
router.push(`/agents/${
|
|
17
|
+
if (permanentId) {
|
|
18
|
+
router.push(`/agents/${permanentId}`);
|
|
18
19
|
} else {
|
|
19
20
|
router.refresh();
|
|
20
21
|
setIsLoading(false);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { $generateBookBoilerplate } from '@promptbook-local/core';
|
|
4
|
-
import { string_agent_name } from '@promptbook-local/types';
|
|
4
|
+
import { string_agent_name, string_book } from '@promptbook-local/types';
|
|
5
5
|
import { revalidatePath } from 'next/cache';
|
|
6
|
-
import {
|
|
6
|
+
import { string_agent_permanent_id } from '../../../../src/types/typeAliases';
|
|
7
7
|
import { getMetadata } from '../database/getMetadata';
|
|
8
8
|
import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
|
|
9
9
|
import { authenticateUser } from '../utils/authenticateUser';
|
|
10
10
|
import { isUserAdmin } from '../utils/isUserAdmin';
|
|
11
11
|
import { clearSession, setSession } from '../utils/session';
|
|
12
12
|
|
|
13
|
-
export async function $createAgentAction(): Promise<string_agent_name> {
|
|
13
|
+
export async function $createAgentAction(): Promise<{ agentName: string_agent_name; permanentId: string_agent_permanent_id }> {
|
|
14
14
|
// TODO: [👹] Check permissions here
|
|
15
15
|
if (!(await isUserAdmin())) {
|
|
16
16
|
throw new Error('You are not authorized to create agents');
|
|
@@ -20,9 +20,21 @@ export async function $createAgentAction(): Promise<string_agent_name> {
|
|
|
20
20
|
const namePool = (await getMetadata('NAME_POOL')) || 'ENGLISH';
|
|
21
21
|
const agentSource = $generateBookBoilerplate({ namePool });
|
|
22
22
|
|
|
23
|
-
const { agentName } = await collection.createAgent(agentSource);
|
|
23
|
+
const { agentName, permanentId } = await collection.createAgent(agentSource);
|
|
24
24
|
|
|
25
|
-
return agentName;
|
|
25
|
+
return { agentName, permanentId };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function $createAgentFromBookAction(bookContent: string_book): Promise<{ agentName: string_agent_name; permanentId: string_agent_permanent_id }> {
|
|
29
|
+
// TODO: [👹] Check permissions here
|
|
30
|
+
if (!(await isUserAdmin())) {
|
|
31
|
+
throw new Error('You are not authorized to create agents');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const collection = await $provideAgentCollectionForServer();
|
|
35
|
+
const { agentName, permanentId } = await collection.createAgent(bookContent);
|
|
36
|
+
|
|
37
|
+
return { agentName, permanentId };
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
export async function loginAction(formData: FormData) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { upload } from '@vercel/blob/client';
|
|
3
4
|
import { FileText, Hash, Image, Shield, ToggleLeft, Type, Upload } from 'lucide-react';
|
|
4
5
|
import { useEffect, useRef, useState } from 'react';
|
|
5
6
|
import { metadataDefaults, MetadataType } from '../../../database/metadataDefaults';
|
|
@@ -185,26 +186,29 @@ export function MetadataClient() {
|
|
|
185
186
|
|
|
186
187
|
try {
|
|
187
188
|
setIsUploading(true);
|
|
188
|
-
const formData = new FormData();
|
|
189
|
-
formData.append('file', file);
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
// Build the full path including prefix and user/files directory
|
|
191
|
+
const pathPrefix = process.env.NEXT_PUBLIC_CDN_PATH_PREFIX || '';
|
|
192
|
+
const uploadPath = pathPrefix ? `${pathPrefix}/user/files/${file.name}` : `user/files/${file.name}`;
|
|
193
|
+
|
|
194
|
+
// Upload directly to Vercel Blob using client upload
|
|
195
|
+
const blob = await upload(uploadPath, file, {
|
|
196
|
+
access: 'public',
|
|
197
|
+
handleUploadUrl: '/api/upload',
|
|
198
|
+
clientPayload: JSON.stringify({
|
|
199
|
+
purpose: formState.key || 'METADATA_IMAGE',
|
|
200
|
+
contentType: file.type,
|
|
201
|
+
}),
|
|
194
202
|
});
|
|
195
203
|
|
|
196
|
-
|
|
197
|
-
throw new Error(`Failed to upload file: ${response.statusText}`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const { fileUrl: longFileUrl } = await response.json();
|
|
204
|
+
const fileUrl = blob.url;
|
|
201
205
|
|
|
202
206
|
const LONG_URL = `${process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!}/${process.env
|
|
203
207
|
.NEXT_PUBLIC_CDN_PATH_PREFIX!}/user/files/`;
|
|
204
208
|
const SHORT_URL = `https://ptbk.io/k/`;
|
|
205
209
|
// <- TODO: [🌍] Unite this logic in one place
|
|
206
210
|
|
|
207
|
-
const shortFileUrl =
|
|
211
|
+
const shortFileUrl = fileUrl.split(LONG_URL).join(SHORT_URL);
|
|
208
212
|
setFormState((prev) => ({ ...prev, value: shortFileUrl }));
|
|
209
213
|
} catch (err) {
|
|
210
214
|
setError(err instanceof Error ? err.message : 'Failed to upload image');
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { TODO_any } from '@promptbook-local/types';
|
|
4
4
|
import {
|
|
5
|
+
CodeIcon,
|
|
5
6
|
CopyIcon,
|
|
6
7
|
CopyPlusIcon,
|
|
7
8
|
DownloadIcon,
|
|
9
|
+
FileTextIcon,
|
|
8
10
|
MailIcon,
|
|
9
11
|
MessageCircleQuestionIcon,
|
|
10
12
|
MessageSquareIcon,
|
|
@@ -13,11 +15,13 @@ import {
|
|
|
13
15
|
QrCodeIcon,
|
|
14
16
|
SmartphoneIcon,
|
|
15
17
|
SquareSplitHorizontalIcon,
|
|
18
|
+
TrashIcon,
|
|
16
19
|
} from 'lucide-react';
|
|
17
20
|
import { Barlow_Condensed } from 'next/font/google';
|
|
18
21
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
19
|
-
import { string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
|
|
22
|
+
import { string_agent_permanent_id, string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
|
|
20
23
|
import { getAgentLinks } from './agentLinks';
|
|
24
|
+
import { deleteAgent } from '../../recycle-bin/actions';
|
|
21
25
|
|
|
22
26
|
type BeforeInstallPromptEvent = Event & {
|
|
23
27
|
prompt: () => Promise<void>;
|
|
@@ -33,6 +37,7 @@ const barlowCondensed = Barlow_Condensed({
|
|
|
33
37
|
type AgentOptionsMenuProps = {
|
|
34
38
|
agentName: string;
|
|
35
39
|
derivedAgentName: string;
|
|
40
|
+
permanentId?: string_agent_permanent_id;
|
|
36
41
|
agentUrl: string;
|
|
37
42
|
agentEmail: string;
|
|
38
43
|
brandColorHex: string;
|
|
@@ -44,6 +49,7 @@ type AgentOptionsMenuProps = {
|
|
|
44
49
|
export function AgentOptionsMenu({
|
|
45
50
|
agentName,
|
|
46
51
|
derivedAgentName,
|
|
52
|
+
permanentId,
|
|
47
53
|
agentUrl,
|
|
48
54
|
agentEmail,
|
|
49
55
|
brandColorHex,
|
|
@@ -115,7 +121,7 @@ export function AgentOptionsMenu({
|
|
|
115
121
|
}
|
|
116
122
|
};
|
|
117
123
|
|
|
118
|
-
const links = getAgentLinks(agentName);
|
|
124
|
+
const links = getAgentLinks(permanentId || agentName);
|
|
119
125
|
const editBookLink = links.find((l) => l.title === 'Edit Book')!;
|
|
120
126
|
const integrationLink = links.find((l) => l.title === 'Integration')!;
|
|
121
127
|
const historyLink = links.find((l) => l.title === 'History & Feedback')!;
|
|
@@ -128,13 +134,29 @@ export function AgentOptionsMenu({
|
|
|
128
134
|
const handleUpdateUrl = () => {
|
|
129
135
|
if (
|
|
130
136
|
window.confirm(
|
|
131
|
-
`Are you sure you want to change the agent URL from "/agents/${agentName}" to "/agents/${derivedAgentName}"
|
|
137
|
+
`Are you sure you want to change the agent URL from "/agents/${agentName}" to "/agents/${derivedAgentName}"?`,
|
|
132
138
|
)
|
|
133
139
|
) {
|
|
134
140
|
window.location.href = updateUrlHref;
|
|
135
141
|
}
|
|
136
142
|
};
|
|
137
143
|
|
|
144
|
+
const handleDeleteAgent = async () => {
|
|
145
|
+
if (
|
|
146
|
+
window.confirm(
|
|
147
|
+
`Are you sure you want to delete the agent "${agentName}"? This action can be undone by restoring it from the recycle bin.`,
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
try {
|
|
151
|
+
await deleteAgent(agentName);
|
|
152
|
+
window.location.href = '/';
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Failed to delete agent:', error);
|
|
155
|
+
alert('Failed to delete agent. Please try again.');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
138
160
|
const menuItems = [
|
|
139
161
|
...(showUpdateUrl
|
|
140
162
|
? [
|
|
@@ -166,6 +188,18 @@ export function AgentOptionsMenu({
|
|
|
166
188
|
icon: editBookLink.icon,
|
|
167
189
|
label: editBookLink.title,
|
|
168
190
|
},
|
|
191
|
+
{
|
|
192
|
+
type: 'link' as const,
|
|
193
|
+
href: `/agents/${encodeURIComponent(agentName)}/system-message`,
|
|
194
|
+
icon: FileTextIcon,
|
|
195
|
+
label: 'System Message',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'link' as const,
|
|
199
|
+
href: `/agents/${encodeURIComponent(agentName)}/code`,
|
|
200
|
+
icon: CodeIcon,
|
|
201
|
+
label: 'View Code',
|
|
202
|
+
},
|
|
169
203
|
{ type: 'divider' as const },
|
|
170
204
|
{
|
|
171
205
|
type: 'link' as const,
|
|
@@ -243,6 +277,12 @@ export function AgentOptionsMenu({
|
|
|
243
277
|
icon: DownloadIcon,
|
|
244
278
|
label: 'Export Agent',
|
|
245
279
|
},
|
|
280
|
+
{
|
|
281
|
+
type: 'action' as const,
|
|
282
|
+
icon: TrashIcon,
|
|
283
|
+
label: 'Delete Agent',
|
|
284
|
+
onClick: handleDeleteAgent,
|
|
285
|
+
},
|
|
246
286
|
// {
|
|
247
287
|
// type: 'link' as const,
|
|
248
288
|
// href: backgroundImage,
|
|
@@ -297,12 +337,16 @@ export function AgentOptionsMenu({
|
|
|
297
337
|
}
|
|
298
338
|
}}
|
|
299
339
|
className={`flex items-center gap-3 px-4 py-2.5 w-full text-left transition-colors
|
|
300
|
-
${
|
|
301
|
-
|
|
302
|
-
|
|
340
|
+
${
|
|
341
|
+
item.highlight
|
|
342
|
+
? 'bg-yellow-100 text-yellow-900 font-bold hover:bg-yellow-200'
|
|
343
|
+
: 'text-gray-700 hover:bg-gray-50'
|
|
344
|
+
}
|
|
303
345
|
`}
|
|
304
346
|
>
|
|
305
|
-
<item.icon
|
|
347
|
+
<item.icon
|
|
348
|
+
className={`w-4 h-4 ${item.highlight ? 'text-yellow-700' : 'text-gray-500'}`}
|
|
349
|
+
/>
|
|
306
350
|
<span className="text-sm font-medium">{item.label}</span>
|
|
307
351
|
</button>
|
|
308
352
|
);
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
import { usePromise } from '@common/hooks/usePromise';
|
|
4
4
|
import { Chat } from '@promptbook-local/components';
|
|
5
5
|
import { RemoteAgent } from '@promptbook-local/core';
|
|
6
|
+
import { string_book } from '@promptbook-local/types';
|
|
6
7
|
import { useRouter } from 'next/navigation';
|
|
7
|
-
import { useCallback, useMemo } from 'react';
|
|
8
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
8
9
|
import spaceTrim from 'spacetrim';
|
|
9
10
|
import { string_agent_url, string_color } from '../../../../../../src/types/typeAliases';
|
|
11
|
+
import { $createAgentFromBookAction } from '../../../app/actions';
|
|
12
|
+
import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
|
|
10
13
|
|
|
11
14
|
type AgentProfileChatProps = {
|
|
12
15
|
agentUrl: string_agent_url;
|
|
@@ -14,10 +17,12 @@ type AgentProfileChatProps = {
|
|
|
14
17
|
fullname: string;
|
|
15
18
|
brandColorHex: string_color;
|
|
16
19
|
avatarSrc: string;
|
|
20
|
+
isDeleted?: boolean;
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex, avatarSrc }: AgentProfileChatProps) {
|
|
23
|
+
export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex, avatarSrc, isDeleted = false }: AgentProfileChatProps) {
|
|
20
24
|
const router = useRouter();
|
|
25
|
+
const [isCreatingAgent, setIsCreatingAgent] = useState(false);
|
|
21
26
|
|
|
22
27
|
const agentPromise = useMemo(
|
|
23
28
|
() =>
|
|
@@ -38,6 +43,21 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
|
|
|
38
43
|
[agentName, router],
|
|
39
44
|
);
|
|
40
45
|
|
|
46
|
+
const handleCreateAgent = useCallback(async (bookContent: string) => {
|
|
47
|
+
setIsCreatingAgent(true);
|
|
48
|
+
try {
|
|
49
|
+
const { permanentId } = await $createAgentFromBookAction(bookContent as string_book);
|
|
50
|
+
if (permanentId) {
|
|
51
|
+
router.push(`/agents/${permanentId}`);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Failed to create agent:', error);
|
|
55
|
+
alert('Failed to create agent. Please try again.');
|
|
56
|
+
} finally {
|
|
57
|
+
setIsCreatingAgent(false);
|
|
58
|
+
}
|
|
59
|
+
}, [router]);
|
|
60
|
+
|
|
41
61
|
const initialMessage = useMemo(() => {
|
|
42
62
|
if (!agent) {
|
|
43
63
|
return 'Loading...';
|
|
@@ -52,6 +72,15 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
|
|
|
52
72
|
);
|
|
53
73
|
}, [agent, fullname, agentName]);
|
|
54
74
|
|
|
75
|
+
// If agent is deleted, show banner instead of chat
|
|
76
|
+
if (isDeleted) {
|
|
77
|
+
return (
|
|
78
|
+
<div className="w-full min-h-[350px] md:min-h-[500px] flex items-center justify-center">
|
|
79
|
+
<DeletedAgentBanner message="This agent has been deleted. You can restore it from the Recycle Bin." />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
55
84
|
// If agent is not loaded yet, we can show a skeleton or just the default Chat structure
|
|
56
85
|
// But to match "same initial message", we need the agent loaded or at least the default fallback.
|
|
57
86
|
// The fallback above matches AgentChat.tsx default.
|
|
@@ -80,6 +109,7 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
|
|
|
80
109
|
},
|
|
81
110
|
]}
|
|
82
111
|
onMessage={handleMessage}
|
|
112
|
+
onCreateAgent={handleCreateAgent}
|
|
83
113
|
isSaveButtonEnabled={false}
|
|
84
114
|
isCopyButtonEnabled={false}
|
|
85
115
|
className="bg-transparent"
|
|
@@ -21,6 +21,7 @@ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
|
|
|
21
21
|
|
|
22
22
|
// Derived agentName from agent data
|
|
23
23
|
const derivedAgentName = agent.agentName;
|
|
24
|
+
const permanentId = agent.permanentId;
|
|
24
25
|
|
|
25
26
|
return (
|
|
26
27
|
<AgentProfile
|
|
@@ -32,6 +33,7 @@ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
|
|
|
32
33
|
<AgentOptionsMenu
|
|
33
34
|
agentName={agentName}
|
|
34
35
|
derivedAgentName={derivedAgentName}
|
|
36
|
+
permanentId={permanentId}
|
|
35
37
|
agentUrl={agentUrl}
|
|
36
38
|
agentEmail={agentEmail}
|
|
37
39
|
brandColorHex={brandColorHex}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { $getTableName } from '@/src/database/$getTableName';
|
|
1
2
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
3
|
import { parseAgentSource } from '@promptbook-local/core';
|
|
4
|
+
import { $provideSupabaseForServer } from '../../../database/$provideSupabaseForServer';
|
|
3
5
|
|
|
4
6
|
export const AGENT_ACTIONS = ['Emails', 'Web chat', 'Read documents', 'Browser', 'WhatsApp', '<Coding/>'];
|
|
5
7
|
|
|
@@ -14,6 +16,22 @@ export async function getAgentProfile(agentName: string) {
|
|
|
14
16
|
return parseAgentSource(agentSource);
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export async function isAgentDeleted(agentName: string): Promise<boolean> {
|
|
20
|
+
const supabase = $provideSupabaseForServer();
|
|
21
|
+
|
|
22
|
+
const result = await supabase
|
|
23
|
+
.from(await $getTableName(`Agent`))
|
|
24
|
+
.select('deletedAt')
|
|
25
|
+
.eq('agentName', agentName)
|
|
26
|
+
.single();
|
|
27
|
+
|
|
28
|
+
if (result.error || !result.data) {
|
|
29
|
+
return false; // If agent doesn't exist or error, consider not deleted
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result.data.deletedAt !== null;
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
/**
|
|
18
36
|
* TODO: Split to multiple files
|
|
19
37
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { string_agent_name, string_agent_permanent_id } from '@promptbook-local/types';
|
|
1
2
|
import {
|
|
2
3
|
BookOpenIcon,
|
|
3
4
|
CodeIcon,
|
|
@@ -18,42 +19,41 @@ type AgentLink = {
|
|
|
18
19
|
rel?: string;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export const getAgentLinks = (
|
|
22
|
-
const encodedName = encodeURIComponent(agentName);
|
|
22
|
+
export const getAgentLinks = (permanentId: string_agent_permanent_id | string_agent_name): AgentLink[] => {
|
|
23
23
|
return [
|
|
24
24
|
{
|
|
25
25
|
title: 'Chat with Agent',
|
|
26
|
-
href: `/agents/${
|
|
26
|
+
href: `/agents/${permanentId}`,
|
|
27
27
|
icon: MessageSquareIcon,
|
|
28
28
|
description: 'Direct interface to converse with the agent.',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
title: 'Edit Book',
|
|
32
|
-
href: `/agents/${
|
|
32
|
+
href: `/agents/${permanentId}/book`,
|
|
33
33
|
icon: NotebookPenIcon,
|
|
34
34
|
description: "Edit the agent's knowledge book.",
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
title: 'Integration',
|
|
38
|
-
href: `/agents/${
|
|
38
|
+
href: `/agents/${permanentId}/integration`,
|
|
39
39
|
icon: CodeIcon,
|
|
40
40
|
description: 'Learn how to integrate this agent into your applications.',
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
title: 'History & Feedback',
|
|
44
|
-
href: `/agents/${
|
|
44
|
+
href: `/agents/${permanentId}/history`,
|
|
45
45
|
icon: HistoryIcon,
|
|
46
46
|
description: 'View past conversations and provide feedback.',
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
title: 'All Links',
|
|
50
|
-
href: `/agents/${
|
|
50
|
+
href: `/agents/${permanentId}/links`,
|
|
51
51
|
icon: LinkIcon,
|
|
52
52
|
description: 'Signpost & Links',
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
title: 'Website Integration',
|
|
56
|
-
href: `/agents/${
|
|
56
|
+
href: `/agents/${permanentId}/website-integration`,
|
|
57
57
|
icon: GlobeIcon,
|
|
58
58
|
description: 'Embed the agent chat widget directly into your React application.',
|
|
59
59
|
},
|
|
@@ -2,15 +2,12 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
|
|
3
3
|
export const dynamic = 'force-dynamic';
|
|
4
4
|
|
|
5
|
-
export async function GET(
|
|
6
|
-
request: NextRequest,
|
|
7
|
-
{ params }: { params: Promise<{ agentName: string }> }
|
|
8
|
-
) {
|
|
5
|
+
export async function GET(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
|
|
9
6
|
try {
|
|
10
7
|
const { agentName } = await params;
|
|
11
8
|
// agentName is likely the federated server URL (e.g., "https://s6.ptbk.io")
|
|
12
9
|
// It comes decoded from the URL params if it was encoded in the request path
|
|
13
|
-
|
|
10
|
+
|
|
14
11
|
let serverUrl = agentName;
|
|
15
12
|
|
|
16
13
|
// If the serverUrl doesn't look like a URL, it might be just a hostname or something else
|
|
@@ -18,20 +15,17 @@ export async function GET(
|
|
|
18
15
|
// The client will likely pass the full URL or hostname.
|
|
19
16
|
// We'll assume if it doesn't start with http, we might need to prepend it, or it's invalid.
|
|
20
17
|
// However, the current federated servers list contains full URLs.
|
|
21
|
-
|
|
18
|
+
|
|
22
19
|
// If it was somehow double encoded or something, we might need to handle it, but standard Next.js behavior is single decode.
|
|
23
|
-
|
|
20
|
+
|
|
24
21
|
if (!serverUrl.startsWith('http')) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{ status: 400 }
|
|
33
|
-
);
|
|
34
|
-
}
|
|
22
|
+
// Maybe it is just a hostname?
|
|
23
|
+
// Let's try to assume https if missing
|
|
24
|
+
if (serverUrl.includes('.')) {
|
|
25
|
+
serverUrl = `https://${serverUrl}`;
|
|
26
|
+
} else {
|
|
27
|
+
return NextResponse.json({ error: 'Invalid federated server URL' }, { status: 400 });
|
|
28
|
+
}
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
// Normalize URL (remove trailing slash)
|
|
@@ -48,20 +42,17 @@ export async function GET(
|
|
|
48
42
|
|
|
49
43
|
if (!response.ok) {
|
|
50
44
|
console.warn(`Proxy failed to fetch agents from ${serverUrl}: ${response.status} ${response.statusText}`);
|
|
51
|
-
return NextResponse.json(
|
|
52
|
-
{ error: `Failed to fetch from ${serverUrl}` },
|
|
53
|
-
{ status: response.status }
|
|
54
|
-
);
|
|
45
|
+
return NextResponse.json({ error: `Failed to fetch from ${serverUrl}` }, { status: response.status });
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
const data = await response.json();
|
|
58
49
|
return NextResponse.json(data);
|
|
59
|
-
|
|
60
50
|
} catch (error) {
|
|
61
51
|
console.error('Proxy error fetching federated agents:', error);
|
|
62
|
-
return NextResponse.json(
|
|
63
|
-
{ error: 'Internal Server Error' },
|
|
64
|
-
{ status: 500 }
|
|
65
|
-
);
|
|
52
|
+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
66
53
|
}
|
|
67
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* TODO: !!!! Probbably remove this route
|
|
58
|
+
*/
|
|
@@ -5,6 +5,7 @@ import { $provideOpenAiAssistantExecutionToolsForServer } from '@/src/tools/$pro
|
|
|
5
5
|
import { Agent, computeAgentHash, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
|
|
6
6
|
import { computeHash, serializeError } from '@promptbook-local/utils';
|
|
7
7
|
import { assertsError } from '../../../../../../../../src/errors/assertsError';
|
|
8
|
+
import { isAgentDeleted } from '../../_utils';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Allow long-running streams: set to platform maximum (seconds)
|
|
@@ -26,6 +27,22 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
|
|
|
26
27
|
let { agentName } = await params;
|
|
27
28
|
agentName = decodeURIComponent(agentName);
|
|
28
29
|
|
|
30
|
+
// Check if agent is deleted
|
|
31
|
+
if (await isAgentDeleted(agentName)) {
|
|
32
|
+
return new Response(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
error: {
|
|
35
|
+
message: 'This agent has been deleted. You can restore it from the Recycle Bin.',
|
|
36
|
+
type: 'agent_deleted',
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
{
|
|
40
|
+
status: 410, // Gone - indicates the resource is no longer available
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
const body = await request.json();
|
|
30
47
|
const { message = 'Tell me more about yourself.', thread } = body;
|
|
31
48
|
// <- TODO: [🐱🚀] To configuration DEFAULT_INITIAL_HIDDEN_MESSAGE
|
|
@@ -27,7 +27,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
27
27
|
const agentSource = await collection.getAgentSource(agentName);
|
|
28
28
|
const agentProfile = parseAgentSource(agentSource);
|
|
29
29
|
const agentHash = computeAgentHash(agentSource);
|
|
30
|
-
const isVoiceCallingEnabled = (await getMetadata('
|
|
30
|
+
const isVoiceCallingEnabled = (await getMetadata('IS_EXPERIMENTAL_VOICE_CALLING_ENABLED')) === 'true';
|
|
31
31
|
|
|
32
32
|
return new Response(
|
|
33
33
|
JSON.stringify(
|
|
@@ -22,7 +22,7 @@ export async function OPTIONS(request: Request) {
|
|
|
22
22
|
|
|
23
23
|
export async function POST(request: Request, { params }: { params: Promise<{ agentName: string }> }) {
|
|
24
24
|
// Check if voice calling is enabled
|
|
25
|
-
const isVoiceCallingEnabled = (await getMetadata('
|
|
25
|
+
const isVoiceCallingEnabled = (await getMetadata('IS_EXPERIMENTAL_VOICE_CALLING_ENABLED')) === 'true';
|
|
26
26
|
if (!isVoiceCallingEnabled) {
|
|
27
27
|
return new Response(JSON.stringify({ error: 'Voice calling is disabled on this server' }), {
|
|
28
28
|
status: 403,
|