@promptbook/cli 0.104.0-0 → 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/WebsiteIntegrationTabs.tsx +26 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +23 -6
- 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/agents/[agentName]/website-integration/page.tsx +35 -18
- 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/embed.js/route.ts +87 -67
- 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/embed/layout.tsx +31 -0
- package/apps/agents-server/src/app/embed/page.tsx +22 -9
- 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
|
@@ -1,43 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { EyeIcon, EyeOffIcon, RotateCcwIcon } from 'lucide-react';
|
|
1
4
|
import Link from 'next/link';
|
|
2
|
-
import React from 'react';
|
|
3
5
|
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
4
|
-
import {
|
|
5
|
-
import { Card } from './Card';
|
|
6
|
+
import { useAgentBackground } from '../AgentProfile/useAgentBackground';
|
|
6
7
|
|
|
7
8
|
type AgentCardProps = {
|
|
8
9
|
agent: AgentBasicInformation;
|
|
9
10
|
href: string;
|
|
10
11
|
isAdmin?: boolean;
|
|
11
|
-
onDelete?: (
|
|
12
|
-
onClone?: (
|
|
12
|
+
onDelete?: (agentIdentifier: string) => void;
|
|
13
|
+
onClone?: (agentIdentifier: string) => void;
|
|
14
|
+
onToggleVisibility?: (agentIdentifier: string) => void;
|
|
15
|
+
onRestore?: (agentIdentifier: string) => void;
|
|
16
|
+
visibility?: 'PUBLIC' | 'PRIVATE';
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
const ACTION_BUTTON_CLASSES =
|
|
16
20
|
'text-white px-3 py-1 rounded shadow text-xs font-medium transition-colors uppercase tracking-wider opacity-80 hover:opacity-100';
|
|
17
21
|
|
|
18
|
-
export function AgentCard({ agent, href, isAdmin, onDelete, onClone }: AgentCardProps) {
|
|
22
|
+
export function AgentCard({ agent, href, isAdmin, onDelete, onClone, onToggleVisibility, onRestore, visibility }: AgentCardProps) {
|
|
23
|
+
const { meta, agentName } = agent;
|
|
24
|
+
const fullname = (meta.fullname as string) || agentName || 'Agent';
|
|
25
|
+
const imageUrl = (meta.image as string) || null;
|
|
26
|
+
const personaDescription = agent.personaDescription || '';
|
|
27
|
+
|
|
28
|
+
const { brandColorLightHex, brandColorDarkHex, backgroundImage } = useAgentBackground(meta.color);
|
|
29
|
+
|
|
19
30
|
return (
|
|
20
31
|
<div className="relative h-full group">
|
|
21
|
-
<Link href={href} className="block h-full">
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
32
|
+
<Link href={href} className="block h-full transition-transform hover:scale-[1.02] duration-300">
|
|
33
|
+
<div
|
|
34
|
+
className="h-full rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 flex flex-col border border-white/20"
|
|
35
|
+
style={{
|
|
36
|
+
background: `url("${backgroundImage}")`,
|
|
37
|
+
backgroundSize: 'cover',
|
|
38
|
+
backgroundPosition: 'center',
|
|
39
|
+
}}
|
|
30
40
|
>
|
|
31
|
-
<
|
|
32
|
-
|
|
41
|
+
<div className="p-6 flex flex-col items-center flex-grow backdrop-blur-[2px]">
|
|
42
|
+
{/* Image container */}
|
|
43
|
+
<div
|
|
44
|
+
className="w-32 h-32 mb-4 rounded-xl shadow-lg overflow-hidden flex-shrink-0 bg-black/20"
|
|
45
|
+
style={{
|
|
46
|
+
boxShadow: `0 10px 20px -5px rgba(0, 0, 0, 0.2), 0 0 0 1px ${brandColorLightHex}40`,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{imageUrl ? (
|
|
50
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
51
|
+
<img src={imageUrl} alt={fullname} className="w-full h-full object-cover" />
|
|
52
|
+
) : (
|
|
53
|
+
<div
|
|
54
|
+
className="w-full h-full flex items-center justify-center text-4xl font-bold text-white/80"
|
|
55
|
+
style={{ backgroundColor: brandColorDarkHex }}
|
|
56
|
+
>
|
|
57
|
+
{fullname.charAt(0).toUpperCase()}
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<h3
|
|
63
|
+
className="text-lg font-bold text-gray-900 text-center leading-tight mb-2"
|
|
64
|
+
style={{ textShadow: '0 1px 2px rgba(255,255,255,0.8)' }}
|
|
65
|
+
>
|
|
66
|
+
{fullname}
|
|
67
|
+
</h3>
|
|
68
|
+
|
|
69
|
+
<p className="text-sm text-gray-800 text-center line-clamp-3 leading-relaxed font-medium mix-blend-hard-light">
|
|
70
|
+
{personaDescription}
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
33
74
|
</Link>
|
|
34
|
-
{isAdmin && (
|
|
75
|
+
{isAdmin && onRestore && (
|
|
35
76
|
<div className="absolute top-2 right-2 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
|
77
|
+
<button
|
|
78
|
+
className={`bg-green-500 hover:bg-green-600 ${ACTION_BUTTON_CLASSES}`}
|
|
79
|
+
onClick={(e) => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
onRestore(agent.permanentId || agent.agentName);
|
|
82
|
+
}}
|
|
83
|
+
title="Restore agent"
|
|
84
|
+
>
|
|
85
|
+
<RotateCcwIcon className="w-3 h-3" />
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
{isAdmin && !onRestore && (
|
|
90
|
+
<div className="absolute top-2 right-2 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
|
91
|
+
<button
|
|
92
|
+
className={`${visibility === 'PUBLIC' ? 'bg-green-500 hover:bg-green-600' : 'bg-gray-500 hover:bg-gray-600'} ${ACTION_BUTTON_CLASSES}`}
|
|
93
|
+
onClick={(e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
onToggleVisibility?.(agent.permanentId || agent.agentName);
|
|
96
|
+
}}
|
|
97
|
+
title={`Make ${visibility === 'PUBLIC' ? 'private' : 'public'}`}
|
|
98
|
+
>
|
|
99
|
+
{visibility === 'PUBLIC' ? <EyeIcon className="w-3 h-3" /> : <EyeOffIcon className="w-3 h-3" />}
|
|
100
|
+
</button>
|
|
36
101
|
<button
|
|
37
102
|
className={`bg-blue-500 hover:bg-blue-600 ${ACTION_BUTTON_CLASSES}`}
|
|
38
103
|
onClick={(e) => {
|
|
39
104
|
e.preventDefault();
|
|
40
|
-
onClone?.(agent.agentName);
|
|
105
|
+
onClone?.(agent.permanentId || agent.agentName);
|
|
41
106
|
}}
|
|
42
107
|
title="Clone agent"
|
|
43
108
|
>
|
|
@@ -47,7 +112,7 @@ export function AgentCard({ agent, href, isAdmin, onDelete, onClone }: AgentCard
|
|
|
47
112
|
className={`bg-red-500 hover:bg-red-600 ${ACTION_BUTTON_CLASSES}`}
|
|
48
113
|
onClick={(e) => {
|
|
49
114
|
e.preventDefault();
|
|
50
|
-
onDelete?.(agent.agentName);
|
|
115
|
+
onDelete?.(agent.permanentId || agent.agentName);
|
|
51
116
|
}}
|
|
52
117
|
title="Delete agent"
|
|
53
118
|
>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
5
6
|
import { TrashIcon } from 'lucide-react';
|
|
6
7
|
import Link from 'next/link';
|
|
7
8
|
import { AddAgentButton } from '../../app/AddAgentButton';
|
|
@@ -10,37 +11,96 @@ import { Section } from './Section';
|
|
|
10
11
|
|
|
11
12
|
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
12
13
|
|
|
14
|
+
type AgentWithVisibility = AgentBasicInformation & {
|
|
15
|
+
visibility?: 'PUBLIC' | 'PRIVATE';
|
|
16
|
+
};
|
|
17
|
+
|
|
13
18
|
type AgentsListProps = {
|
|
14
|
-
agents:
|
|
19
|
+
agents: AgentWithVisibility[];
|
|
15
20
|
isAdmin: boolean;
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
export function AgentsList({ agents: initialAgents, isAdmin }: AgentsListProps) {
|
|
24
|
+
const router = useRouter();
|
|
19
25
|
const [agents, setAgents] = useState(Array.from(initialAgents));
|
|
20
26
|
|
|
21
|
-
const handleDelete = async (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const handleDelete = async (agentIdentifier: string) => {
|
|
28
|
+
const agent = agents.find(a => a.permanentId === agentIdentifier || a.agentName === agentIdentifier);
|
|
29
|
+
if (!agent) return;
|
|
30
|
+
if (!window.confirm(`Delete agent "${agent.agentName}"? It will be moved to Recycle Bin.`)) return;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentIdentifier)}`, { method: 'DELETE' });
|
|
34
|
+
if (response.ok) {
|
|
35
|
+
// Update local state immediately
|
|
36
|
+
setAgents(agents.filter((a) => a.permanentId !== agent.permanentId && a.agentName !== agent.agentName));
|
|
37
|
+
// Note: router.refresh() is not needed here as the local state update is sufficient
|
|
38
|
+
// and prevents the brief empty list issue during refresh
|
|
39
|
+
} else {
|
|
40
|
+
alert('Failed to delete agent');
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
alert('Failed to delete agent');
|
|
44
|
+
}
|
|
25
45
|
};
|
|
26
46
|
|
|
27
|
-
const handleClone = async (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
const handleClone = async (agentIdentifier: string) => {
|
|
48
|
+
const agent = agents.find(a => a.permanentId === agentIdentifier || a.agentName === agentIdentifier);
|
|
49
|
+
if (!agent) return;
|
|
50
|
+
if (!window.confirm(`Clone agent "${agent.agentName}"?`)) return;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentIdentifier)}/clone`, { method: 'POST' });
|
|
54
|
+
if (response.ok) {
|
|
55
|
+
const newAgent = await response.json();
|
|
56
|
+
setAgents([...agents, newAgent]);
|
|
57
|
+
router.refresh(); // Refresh server data to ensure consistency
|
|
58
|
+
} else {
|
|
59
|
+
alert('Failed to clone agent');
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
alert('Failed to clone agent');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleToggleVisibility = async (agentIdentifier: string) => {
|
|
67
|
+
const agent = agents.find(a => a.permanentId === agentIdentifier || a.agentName === agentIdentifier);
|
|
68
|
+
if (!agent) return;
|
|
69
|
+
|
|
70
|
+
const newVisibility = agent.visibility === 'PUBLIC' ? 'PRIVATE' : 'PUBLIC';
|
|
71
|
+
if (!window.confirm(`Make agent "${agent.agentName}" ${newVisibility.toLowerCase()}?`)) return;
|
|
72
|
+
|
|
73
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentIdentifier)}`, {
|
|
74
|
+
method: 'PATCH',
|
|
75
|
+
headers: { 'Content-Type': 'application/json' },
|
|
76
|
+
body: JSON.stringify({ visibility: newVisibility }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (response.ok) {
|
|
80
|
+
// Update the local state
|
|
81
|
+
setAgents(agents.map(a =>
|
|
82
|
+
a.permanentId === agent.permanentId || a.agentName === agent.agentName
|
|
83
|
+
? { ...a, visibility: newVisibility }
|
|
84
|
+
: a
|
|
85
|
+
));
|
|
86
|
+
router.refresh(); // Refresh server data to ensure consistency
|
|
87
|
+
} else {
|
|
88
|
+
alert('Failed to update agent visibility');
|
|
89
|
+
}
|
|
32
90
|
};
|
|
33
91
|
|
|
34
92
|
return (
|
|
35
93
|
<Section title={`Agents (${agents.length})`}>
|
|
36
94
|
{agents.map((agent) => (
|
|
37
95
|
<AgentCard
|
|
38
|
-
key={agent.agentName}
|
|
96
|
+
key={agent.permanentId || agent.agentName}
|
|
39
97
|
agent={agent}
|
|
40
|
-
href={
|
|
98
|
+
href={`/agents/${encodeURIComponent(agent.permanentId || agent.agentName)}`}
|
|
41
99
|
isAdmin={isAdmin}
|
|
42
100
|
onDelete={handleDelete}
|
|
43
101
|
onClone={handleClone}
|
|
102
|
+
onToggleVisibility={handleToggleVisibility}
|
|
103
|
+
visibility={agent.visibility}
|
|
44
104
|
/>
|
|
45
105
|
))}
|
|
46
106
|
{isAdmin && <AddAgentButton />}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Client Component for rendering deleted agents
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import React, { useState } from 'react';
|
|
5
|
+
import { AgentCard } from './AgentCard';
|
|
6
|
+
|
|
7
|
+
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
8
|
+
|
|
9
|
+
type DeletedAgentsListProps = {
|
|
10
|
+
agents: readonly AgentBasicInformation[];
|
|
11
|
+
isAdmin: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function DeletedAgentsList({ agents: initialAgents, isAdmin }: DeletedAgentsListProps) {
|
|
15
|
+
const [agents, setAgents] = useState(Array.from(initialAgents));
|
|
16
|
+
|
|
17
|
+
const handleRestore = async (agentIdentifier: string) => {
|
|
18
|
+
const agent = agents.find(a => a.permanentId === agentIdentifier || a.agentName === agentIdentifier);
|
|
19
|
+
if (!agent) return;
|
|
20
|
+
if (!window.confirm(`Restore agent "${agent.agentName}"?`)) return;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentIdentifier)}/restore`, { method: 'POST' });
|
|
24
|
+
if (response.ok) {
|
|
25
|
+
// Update local state immediately
|
|
26
|
+
setAgents(agents.filter((a) => a.permanentId !== agent.permanentId && a.agentName !== agent.agentName));
|
|
27
|
+
// Note: router.refresh() is not needed here as the local state update is sufficient
|
|
28
|
+
// and prevents the brief empty list issue during refresh
|
|
29
|
+
} else {
|
|
30
|
+
alert('Failed to restore agent');
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
alert('Failed to restore agent');
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
39
|
+
{agents.map((agent) => (
|
|
40
|
+
<AgentCard
|
|
41
|
+
key={agent.permanentId || agent.agentName}
|
|
42
|
+
agent={agent}
|
|
43
|
+
href={`/agents/${encodeURIComponent(agent.permanentId || agent.agentName)}`}
|
|
44
|
+
isAdmin={isAdmin}
|
|
45
|
+
onRestore={handleRestore}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
4
4
|
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
5
|
+
import { MenuHoistingProvider } from '../../../../../src/book-components/_common/MenuHoisting/MenuHoistingContext';
|
|
5
6
|
import type { UserInfo } from '../../utils/getCurrentUser';
|
|
6
7
|
import { Footer, type FooterLink } from '../Footer/Footer';
|
|
7
8
|
import { Header } from '../Header/Header';
|
|
@@ -41,7 +42,7 @@ export function LayoutWrapper({
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
return (
|
|
44
|
-
|
|
45
|
+
<MenuHoistingProvider>
|
|
45
46
|
<Header
|
|
46
47
|
isAdmin={isAdmin}
|
|
47
48
|
currentUser={currentUser}
|
|
@@ -52,6 +53,6 @@ export function LayoutWrapper({
|
|
|
52
53
|
/>
|
|
53
54
|
<main className={`pt-[60px]`}>{children}</main>
|
|
54
55
|
{isFooterShown && !isFooterHiddenOnPage && <Footer extraLinks={footerLinks} />}
|
|
55
|
-
|
|
56
|
+
</MenuHoistingProvider>
|
|
56
57
|
);
|
|
57
58
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { loginAction } from '@/src/app/actions';
|
|
4
|
+
import { ForgottenPasswordDialog } from '../ForgottenPasswordDialog/ForgottenPasswordDialog';
|
|
5
|
+
import { RegisterUserDialog } from '../RegisterUserDialog/RegisterUserDialog';
|
|
4
6
|
import { Loader2, Lock, User } from 'lucide-react';
|
|
5
|
-
import { useState } from 'react';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
6
8
|
|
|
7
9
|
type LoginFormProps = {
|
|
8
10
|
onSuccess?: () => void;
|
|
@@ -13,6 +15,24 @@ export function LoginForm(props: LoginFormProps) {
|
|
|
13
15
|
const { onSuccess, className } = props;
|
|
14
16
|
const [isLoading, setIsLoading] = useState(false);
|
|
15
17
|
const [error, setError] = useState<string | null>(null);
|
|
18
|
+
const [adminEmail, setAdminEmail] = useState<string>('support@ptbk.io');
|
|
19
|
+
const [isForgottenPasswordOpen, setIsForgottenPasswordOpen] = useState(false);
|
|
20
|
+
const [isRegisterUserOpen, setIsRegisterUserOpen] = useState(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
// Fetch admin email on component mount
|
|
24
|
+
fetch('/api/admin-email')
|
|
25
|
+
.then(response => response.json())
|
|
26
|
+
.then(data => {
|
|
27
|
+
if (data.adminEmail) {
|
|
28
|
+
setAdminEmail(data.adminEmail);
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.catch(error => {
|
|
32
|
+
console.error('Failed to fetch admin email:', error);
|
|
33
|
+
// Keep default value
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
16
36
|
|
|
17
37
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
18
38
|
event.preventDefault();
|
|
@@ -104,6 +124,35 @@ export function LoginForm(props: LoginFormProps) {
|
|
|
104
124
|
'Log in'
|
|
105
125
|
)}
|
|
106
126
|
</button>
|
|
127
|
+
|
|
128
|
+
<div className="flex justify-between text-sm">
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => setIsForgottenPasswordOpen(true)}
|
|
132
|
+
className="text-promptbook-blue hover:text-promptbook-blue-dark underline focus:outline-none focus:ring-2 focus:ring-promptbook-blue focus:ring-offset-2 rounded-sm"
|
|
133
|
+
>
|
|
134
|
+
Forgotten password?
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => setIsRegisterUserOpen(true)}
|
|
139
|
+
className="text-promptbook-blue hover:text-promptbook-blue-dark underline focus:outline-none focus:ring-2 focus:ring-promptbook-blue focus:ring-offset-2 rounded-sm"
|
|
140
|
+
>
|
|
141
|
+
Register new user
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<ForgottenPasswordDialog
|
|
146
|
+
isOpen={isForgottenPasswordOpen}
|
|
147
|
+
onClose={() => setIsForgottenPasswordOpen(false)}
|
|
148
|
+
adminEmail={adminEmail}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<RegisterUserDialog
|
|
152
|
+
isOpen={isRegisterUserOpen}
|
|
153
|
+
onClose={() => setIsRegisterUserOpen(false)}
|
|
154
|
+
adminEmail={adminEmail}
|
|
155
|
+
/>
|
|
107
156
|
</form>
|
|
108
157
|
);
|
|
109
158
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import { HomeIcon } from 'lucide-react';
|
|
1
2
|
import Link from 'next/link';
|
|
2
3
|
import { ErrorPage } from '../ErrorPage/ErrorPage';
|
|
3
4
|
|
|
4
5
|
export function NotFoundPage() {
|
|
5
6
|
return (
|
|
6
|
-
<ErrorPage
|
|
7
|
+
<ErrorPage
|
|
8
|
+
title="Agent Not Found :("
|
|
9
|
+
message="The agent you are looking for does not exist, but you can create your own!"
|
|
10
|
+
>
|
|
7
11
|
<div className="flex justify-center">
|
|
8
12
|
<Link
|
|
9
13
|
href="/"
|
|
10
14
|
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
|
11
15
|
>
|
|
12
|
-
|
|
16
|
+
<HomeIcon className="inline w-5 h-5 mr-2" />
|
|
17
|
+
Home
|
|
13
18
|
</Link>
|
|
14
19
|
</div>
|
|
15
20
|
</ErrorPage>
|
|
@@ -1,19 +1,28 @@
|
|
|
1
|
+
import { string_char_emoji } from '@promptbook-local/types';
|
|
1
2
|
import { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
2
3
|
|
|
3
4
|
type OpenMojiIconProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The OpenMoji character to display
|
|
7
|
+
*/
|
|
8
|
+
icon: string_char_emoji | string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @default 'black'
|
|
12
|
+
*/
|
|
13
|
+
variant?: 'black' | 'color';
|
|
5
14
|
};
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* Renders an emoji using the OpenMoji black and white font
|
|
9
18
|
*/
|
|
10
|
-
export function OpenMojiIcon(
|
|
19
|
+
export function OpenMojiIcon(props: OpenMojiIconProps) {
|
|
20
|
+
const { icon, variant = 'black', className, style, ...rest } = props;
|
|
21
|
+
|
|
22
|
+
const fontFamily = variant === 'black' ? '"OpenMojiBlack", sans-serif' : '"OpenMojiColor", sans-serif';
|
|
23
|
+
|
|
11
24
|
return (
|
|
12
|
-
<span
|
|
13
|
-
className={className}
|
|
14
|
-
style={{ ...style, fontFamily: '"OpenMojiBlack", sans-serif' }}
|
|
15
|
-
{...rest}
|
|
16
|
-
>
|
|
25
|
+
<span className={className} style={{ ...style, fontFamily }} {...rest}>
|
|
17
26
|
{icon}
|
|
18
27
|
</span>
|
|
19
28
|
);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export function PrintHeader({ title }: { title?: string }) {
|
|
2
2
|
return (
|
|
3
|
-
<div className="hidden print:block mb-
|
|
3
|
+
<div className="hidden print:block mb-6 border-b-2 border-blue-600 pb-2">
|
|
4
4
|
<div className="flex justify-between items-end">
|
|
5
5
|
<div>
|
|
6
|
-
<h1 className="text-
|
|
6
|
+
<h1 className="text-2xl font-bold text-gray-900 font-poppins">Agents Server</h1>
|
|
7
7
|
<div className="text-sm text-gray-500 mt-1 flex items-center gap-1">
|
|
8
8
|
Powered by <span className="font-semibold text-blue-600">Promptbook</span>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
11
|
-
{title && <h2 className="text-
|
|
11
|
+
{title && <h2 className="text-lg font-semibold text-gray-700">{title}</h2>}
|
|
12
12
|
</div>
|
|
13
|
-
<div className="text-xs text-gray-400 mt-
|
|
13
|
+
<div className="text-xs text-gray-400 mt-1 text-right">
|
|
14
14
|
{new Date().toLocaleDateString()}
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { Portal } from '../Portal/Portal';
|
|
5
|
+
|
|
6
|
+
type RegisterUserDialogProps = {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
adminEmail: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function RegisterUserDialog(props: RegisterUserDialogProps) {
|
|
13
|
+
const { isOpen, onClose, adminEmail } = props;
|
|
14
|
+
|
|
15
|
+
if (!isOpen) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Portal>
|
|
21
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in duration-200">
|
|
22
|
+
<div className="relative w-full max-w-md bg-white rounded-lg shadow-lg border border-gray-200 p-6 animate-in zoom-in-95 duration-200">
|
|
23
|
+
<button
|
|
24
|
+
onClick={onClose}
|
|
25
|
+
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 transition-colors"
|
|
26
|
+
>
|
|
27
|
+
<X className="w-5 h-5" />
|
|
28
|
+
<span className="sr-only">Close</span>
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<div className="mb-6">
|
|
32
|
+
<h2 className="text-xl font-semibold text-gray-900">Register New User</h2>
|
|
33
|
+
<p className="text-sm text-gray-500 mt-1">Create a new user account</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="space-y-4">
|
|
37
|
+
<div className="p-4 bg-blue-50 border border-blue-200 rounded-md">
|
|
38
|
+
<p className="text-sm text-blue-800">
|
|
39
|
+
This Promptbook server has no email capability. Please contact the administrator at{' '}
|
|
40
|
+
<a
|
|
41
|
+
href={`mailto:${adminEmail}`}
|
|
42
|
+
className="font-medium text-blue-900 underline hover:text-blue-800"
|
|
43
|
+
>
|
|
44
|
+
{adminEmail}
|
|
45
|
+
</a>{' '}
|
|
46
|
+
to register new user.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<button
|
|
51
|
+
onClick={onClose}
|
|
52
|
+
className="w-full inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-gray-100 text-gray-900 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors"
|
|
53
|
+
>
|
|
54
|
+
Close
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</Portal>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -38,7 +38,13 @@ export const metadataDefaults = [
|
|
|
38
38
|
type: 'TEXT',
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
key: '
|
|
41
|
+
key: 'SHOW_FEDERATED_SERVERS_PUBLICLY',
|
|
42
|
+
value: 'false',
|
|
43
|
+
note: 'Whether to show federated servers and their agents to anonymous users. When false, federated servers are only visible to authenticated users.',
|
|
44
|
+
type: 'BOOLEAN',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: 'IS_EXPERIMENTAL_VOICE_CALLING_ENABLED',
|
|
42
48
|
value: 'false',
|
|
43
49
|
note: 'Enable or disable voice calling features for agents. When disabled, voice API endpoints will return 403 Forbidden.',
|
|
44
50
|
type: 'BOOLEAN',
|
|
@@ -67,6 +73,18 @@ export const metadataDefaults = [
|
|
|
67
73
|
note: 'Language for generating new agent names. Possible values: ENGLISH, CZECH.',
|
|
68
74
|
type: 'TEXT_SINGLE_LINE',
|
|
69
75
|
},
|
|
76
|
+
{
|
|
77
|
+
key: 'ADMIN_EMAIL',
|
|
78
|
+
value: 'support@ptbk.io',
|
|
79
|
+
note: 'Administrator email address used for password reset and user registration requests.',
|
|
80
|
+
type: 'TEXT_SINGLE_LINE',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'DEFAULT_AGENT_VISIBILITY',
|
|
84
|
+
value: 'PRIVATE',
|
|
85
|
+
note: 'Default visibility for new agents. Can be PUBLIC or PRIVATE.',
|
|
86
|
+
type: 'TEXT_SINGLE_LINE',
|
|
87
|
+
},
|
|
70
88
|
] as const satisfies ReadonlyArray<{
|
|
71
89
|
key: string;
|
|
72
90
|
value: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "prefix_Agent" ADD COLUMN "deletedAt" TEXT;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "prefix_Image" (
|
|
2
|
+
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
3
|
+
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
4
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
5
|
+
|
|
6
|
+
"filename" TEXT NOT NULL,
|
|
7
|
+
"prompt" TEXT NOT NULL,
|
|
8
|
+
"cdnUrl" TEXT NOT NULL,
|
|
9
|
+
"cdnKey" TEXT NOT NULL
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "prefix_Image_filename_idx" ON "prefix_Image" ("filename");
|
|
13
|
+
|
|
14
|
+
ALTER TABLE "prefix_Image" ENABLE ROW LEVEL SECURITY;
|
|
15
|
+
|
|
16
|
+
COMMENT ON COLUMN "prefix_Image"."filename" IS 'The original filename requested (e.g., cat-sitting-on-keyboard.png)';
|
|
17
|
+
COMMENT ON COLUMN "prefix_Image"."prompt" IS 'The normalized prompt used to generate the image';
|
|
18
|
+
COMMENT ON COLUMN "prefix_Image"."cdnUrl" IS 'The full URL of the uploaded image in CDN';
|
|
19
|
+
COMMENT ON COLUMN "prefix_Image"."cdnKey" IS 'The key used to identify the image in CDN storage';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "prefix_Agent" ADD COLUMN "visibility" TEXT NOT NULL DEFAULT 'PRIVATE' CHECK ("visibility" IN ('PUBLIC', 'PRIVATE'));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "prefix_File" (
|
|
2
|
+
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
3
|
+
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
4
|
+
|
|
5
|
+
"userId" BIGINT REFERENCES "prefix_User"("id"),
|
|
6
|
+
"fileName" TEXT NOT NULL,
|
|
7
|
+
"fileSize" BIGINT NOT NULL,
|
|
8
|
+
"fileType" TEXT NOT NULL,
|
|
9
|
+
"cdnUrl" TEXT NOT NULL,
|
|
10
|
+
"purpose" TEXT NOT NULL
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
ALTER TABLE "prefix_File" ENABLE ROW LEVEL SECURITY;
|
|
14
|
+
|
|
15
|
+
COMMENT ON COLUMN "prefix_File"."userId" IS 'Reference to the user who uploaded the file';
|
|
16
|
+
COMMENT ON COLUMN "prefix_File"."fileName" IS 'Original name of the uploaded file';
|
|
17
|
+
COMMENT ON COLUMN "prefix_File"."fileSize" IS 'Size of the file in bytes';
|
|
18
|
+
COMMENT ON COLUMN "prefix_File"."fileType" IS 'MIME type of the file';
|
|
19
|
+
COMMENT ON COLUMN "prefix_File"."cdnUrl" IS 'Public URL of the file in CDN';
|
|
20
|
+
COMMENT ON COLUMN "prefix_File"."purpose" IS 'Purpose of the upload (e.g. KNOWLEDGE, SERVER_FAVICON_URL)';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Add status column to track file upload progress
|
|
2
|
+
ALTER TABLE "prefix_File" ADD COLUMN IF NOT EXISTS "status" TEXT NOT NULL DEFAULT 'COMPLETED';
|
|
3
|
+
|
|
4
|
+
-- Add check constraint for valid status values
|
|
5
|
+
-- ALTER TABLE "prefix_File" ADD CONSTRAINT "File_status_check" CHECK ("status" IN ('UPLOADING', 'COMPLETED', 'FAILED'));
|
|
6
|
+
|
|
7
|
+
-- Drop the column cdnUrl if it exists
|
|
8
|
+
ALTER TABLE "prefix_File" DROP COLUMN IF EXISTS "cdnUrl";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
-- Add nullable columns storageUrl and shortUrl
|
|
12
|
+
ALTER TABLE "prefix_File" ADD COLUMN IF NOT EXISTS "storageUrl" TEXT NULL;
|
|
13
|
+
ALTER TABLE "prefix_File" ADD COLUMN IF NOT EXISTS "shortUrl" TEXT NULL;
|