@promptbook/cli 0.104.0-0 → 0.104.0-10
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/config.ts +1 -3
- package/apps/agents-server/next.config.ts +2 -2
- package/apps/agents-server/package.json +7 -3
- package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
- package/apps/agents-server/public/swagger.json +115 -0
- package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
- package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +47 -21
- package/apps/agents-server/src/app/actions.ts +22 -5
- package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +211 -0
- package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
- package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
- package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
- package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
- package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
- package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
- 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/book/route.ts +4 -2
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
- 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 +68 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -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]/history/page.tsx +10 -3
- package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
- package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
- package/apps/agents-server/src/app/agents/[agentName]/integration/WebsiteIntegrationTabs.tsx +26 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +32 -8
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
- package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -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]/clone/route.ts +13 -14
- package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
- package/apps/agents-server/src/app/api/agents/route.ts +28 -3
- package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
- package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
- package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
- package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
- package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
- package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -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/messages/route.ts +102 -0
- package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
- package/apps/agents-server/src/app/api/upload/route.ts +128 -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/humans.txt/route.ts +1 -1
- package/apps/agents-server/src/app/layout.tsx +27 -22
- package/apps/agents-server/src/app/page.tsx +54 -6
- package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
- package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
- package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
- package/apps/agents-server/src/app/security.txt/route.ts +1 -1
- package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
- package/apps/agents-server/src/app/swagger/page.tsx +14 -0
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +41 -116
- package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
- package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
- package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
- package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
- 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 +114 -40
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +145 -23
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
- package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
- 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/NewAgentDialog/NewAgentDialog.tsx +88 -0
- 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/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
- package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
- package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
- package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
- package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
- package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
- package/apps/agents-server/src/database/migrate.ts +34 -1
- package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
- package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
- 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-0402-message-table.sql +42 -0
- package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
- package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
- package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
- package/apps/agents-server/src/database/schema.ts +231 -4
- package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
- package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
- package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
- package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
- package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
- package/apps/agents-server/src/message-providers/index.ts +13 -0
- package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
- package/apps/agents-server/src/middleware.ts +19 -23
- package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
- package/apps/agents-server/src/utils/auth.ts +117 -17
- 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/content/extractBodyContentFromHtml.ts +19 -0
- package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
- package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
- package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
- package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
- package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
- package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
- package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
- package/esm/index.es.js +2890 -2737
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +8 -0
- package/esm/typings/src/_packages/core.index.d.ts +2 -0
- package/esm/typings/src/_packages/types.index.d.ts +10 -2
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
- package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
- package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
- 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/Chat/types/ChatMessage.d.ts +7 -11
- 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 +21 -11
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +80 -14
- 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/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -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/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
- package/esm/typings/src/types/Message.d.ts +49 -0
- package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
- package/esm/typings/src/types/typeAliases.d.ts +23 -1
- package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
- 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 +4018 -3865
- 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
- package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
4
|
+
import Editor from '@monaco-editor/react';
|
|
5
|
+
import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
|
|
6
|
+
import { AgentBasicInformation } from '@promptbook-local/types';
|
|
7
|
+
import { ArrowLeftIcon, ChevronDownIcon, CodeIcon } from 'lucide-react';
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
10
|
+
|
|
11
|
+
type Transpiler = {
|
|
12
|
+
name: string;
|
|
13
|
+
title: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TranspilationResult = {
|
|
17
|
+
code: string;
|
|
18
|
+
transpiler: Transpiler;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getLanguageFromTranspiler(transpilerName?: string): string {
|
|
22
|
+
if (!transpilerName) return 'plaintext';
|
|
23
|
+
|
|
24
|
+
// Map transpiler names to Monaco language identifiers
|
|
25
|
+
if (transpilerName.includes('openai-sdk')) return 'javascript';
|
|
26
|
+
if (transpilerName.includes('langchain') || transpilerName.includes('python')) return 'python';
|
|
27
|
+
if (transpilerName.includes('markdown')) return 'markdown';
|
|
28
|
+
|
|
29
|
+
// Default to plaintext for unknown transpilers
|
|
30
|
+
return 'plaintext';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function AgentCodePage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
34
|
+
const [agentName, setAgentName] = useState<string>('');
|
|
35
|
+
const [agentProfile, setAgentProfile] = useState<AgentBasicInformation | null>(null);
|
|
36
|
+
const [transpilers, setTranspilers] = useState<Transpiler[]>([]);
|
|
37
|
+
const [selectedTranspiler, setSelectedTranspiler] = useState<Transpiler | null>(null);
|
|
38
|
+
const [transpiledCode, setTranspiledCode] = useState<string>('');
|
|
39
|
+
const [loading, setLoading] = useState(false);
|
|
40
|
+
const [error, setError] = useState<string>('');
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
params.then((p) => setAgentName(p.agentName));
|
|
44
|
+
}, [params]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!agentName) return;
|
|
48
|
+
|
|
49
|
+
// Fetch agent profile
|
|
50
|
+
fetch(`/api/agents/${encodeURIComponent(agentName)}`)
|
|
51
|
+
.then((res) => res.json())
|
|
52
|
+
.then((data) => setAgentProfile(data))
|
|
53
|
+
.catch((err) => console.error('Error fetching agent profile:', err));
|
|
54
|
+
|
|
55
|
+
// Fetch available transpilers
|
|
56
|
+
fetch(`/agents/${encodeURIComponent(agentName)}/code/api`)
|
|
57
|
+
.then((res) => res.json())
|
|
58
|
+
.then((data) => {
|
|
59
|
+
setTranspilers(data.transpilers || []);
|
|
60
|
+
if (data.transpilers && data.transpilers.length > 0) {
|
|
61
|
+
setSelectedTranspiler(data.transpilers[0]);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => console.error('Error fetching transpilers:', err));
|
|
65
|
+
}, [agentName]);
|
|
66
|
+
|
|
67
|
+
const transpileCode = useCallback(
|
|
68
|
+
async (transpilerName: string) => {
|
|
69
|
+
setLoading(true);
|
|
70
|
+
setError('');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(`/agents/${encodeURIComponent(agentName)}/code/api`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({ transpilerName }),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const errorData = await response.json();
|
|
83
|
+
throw new Error(errorData.error || 'Failed to transpile code');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result: TranspilationResult = await response.json();
|
|
87
|
+
setTranspiledCode(result.code);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
setError(err instanceof Error ? err.message : 'Failed to transpile code');
|
|
90
|
+
setTranspiledCode('');
|
|
91
|
+
} finally {
|
|
92
|
+
setLoading(false);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[agentName],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (selectedTranspiler && agentName) {
|
|
100
|
+
transpileCode(selectedTranspiler.name);
|
|
101
|
+
}
|
|
102
|
+
}, [selectedTranspiler, agentName, transpileCode]);
|
|
103
|
+
|
|
104
|
+
if (!agentProfile) {
|
|
105
|
+
return (
|
|
106
|
+
<div className="min-h-screen p-6 md:p-12 flex flex-col items-center bg-gray-50">
|
|
107
|
+
<div className="w-full max-w-4xl bg-white rounded-xl shadow-sm border border-gray-200 p-12">
|
|
108
|
+
<div className="text-center">Loading...</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="min-h-screen p-6 md:p-12 flex flex-col items-center bg-gray-50">
|
|
116
|
+
<div className="w-full max-w-4xl bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
117
|
+
{/* Header */}
|
|
118
|
+
<div className="p-6 border-b border-gray-200 flex items-center gap-4">
|
|
119
|
+
{/* eslint-disable @typescript-eslint/no-explicit-any, @next/next/no-img-element */}
|
|
120
|
+
|
|
121
|
+
<img
|
|
122
|
+
src={
|
|
123
|
+
agentProfile.meta.image ||
|
|
124
|
+
generatePlaceholderAgentProfileImageUrl(
|
|
125
|
+
agentProfile.permanentId || agentName,
|
|
126
|
+
NEXT_PUBLIC_SITE_URL, // <- TODO: !!!! Use here `const { publicUrl } = await $provideServer();`
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
alt={agentProfile.meta.fullname || agentName}
|
|
130
|
+
className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
<div className="flex-1">
|
|
134
|
+
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
135
|
+
<h1 className="text-2xl font-bold text-gray-900">
|
|
136
|
+
{(agentProfile as any)?.meta?.fullname || agentName}
|
|
137
|
+
</h1>
|
|
138
|
+
<p className="text-gray-500 flex items-center gap-2">
|
|
139
|
+
<CodeIcon className="w-4 h-4" />
|
|
140
|
+
Generated Code
|
|
141
|
+
</p>
|
|
142
|
+
</div>
|
|
143
|
+
<Link
|
|
144
|
+
href={`/agents/${encodeURIComponent(agentName)}`}
|
|
145
|
+
className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors"
|
|
146
|
+
title="Back to Agent"
|
|
147
|
+
>
|
|
148
|
+
<ArrowLeftIcon className="w-6 h-6" />
|
|
149
|
+
</Link>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div className="p-6">
|
|
153
|
+
{/* Transpiler Selector */}
|
|
154
|
+
<div className="mb-6">
|
|
155
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">Select Transpiler</label>
|
|
156
|
+
<div className="relative">
|
|
157
|
+
<select
|
|
158
|
+
value={selectedTranspiler?.name || ''}
|
|
159
|
+
onChange={(e) => {
|
|
160
|
+
const transpiler = transpilers.find((t) => t.name === e.target.value);
|
|
161
|
+
if (transpiler) setSelectedTranspiler(transpiler);
|
|
162
|
+
}}
|
|
163
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
164
|
+
>
|
|
165
|
+
{transpilers.map((transpiler) => (
|
|
166
|
+
<option key={transpiler.name} value={transpiler.name}>
|
|
167
|
+
{transpiler.title}
|
|
168
|
+
</option>
|
|
169
|
+
))}
|
|
170
|
+
</select>
|
|
171
|
+
<ChevronDownIcon className="absolute right-3 top-3 w-4 h-4 text-gray-400 pointer-events-none" />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Code Display */}
|
|
176
|
+
<div className="bg-gray-50 rounded-lg border border-gray-200 overflow-hidden">
|
|
177
|
+
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
|
178
|
+
<h2 className="text-lg font-semibold text-gray-900">
|
|
179
|
+
{selectedTranspiler?.title || 'Generated Code'}
|
|
180
|
+
</h2>
|
|
181
|
+
{loading && <div className="text-sm text-gray-500">Generating...</div>}
|
|
182
|
+
</div>
|
|
183
|
+
<div className="p-4">
|
|
184
|
+
{error && (
|
|
185
|
+
<div className="text-red-600 text-sm mb-4 p-3 bg-red-50 rounded border border-red-200">
|
|
186
|
+
{error}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
{transpiledCode ? (
|
|
190
|
+
<div className="h-96 border border-gray-200 rounded">
|
|
191
|
+
<Editor
|
|
192
|
+
value={transpiledCode}
|
|
193
|
+
language={getLanguageFromTranspiler(selectedTranspiler?.name)}
|
|
194
|
+
options={{
|
|
195
|
+
readOnly: true,
|
|
196
|
+
minimap: { enabled: false },
|
|
197
|
+
fontSize: 14,
|
|
198
|
+
lineNumbers: 'on',
|
|
199
|
+
scrollBeyondLastLine: false,
|
|
200
|
+
automaticLayout: true,
|
|
201
|
+
wordWrap: 'on',
|
|
202
|
+
}}
|
|
203
|
+
loading={
|
|
204
|
+
<div className="flex items-center justify-center h-full text-gray-500">
|
|
205
|
+
Loading editor...
|
|
206
|
+
</div>
|
|
207
|
+
}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
) : loading ? (
|
|
211
|
+
<div className="text-gray-500 text-center py-8">Generating code...</div>
|
|
212
|
+
) : (
|
|
213
|
+
<div className="text-gray-500 text-center py-8">
|
|
214
|
+
Select a transpiler to generate code
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -15,6 +15,8 @@ export async function generateAgentMetadata({ params }: { params: Promise<{ agen
|
|
|
15
15
|
// Use the agent's icon-256.png as the favicon
|
|
16
16
|
const iconUrl = `/agents/${encodeURIComponent(agentName)}/images/icon-256.png`;
|
|
17
17
|
|
|
18
|
+
const canonicalUrl = `/agents/${encodeURIComponent(agentProfile.permanentId || agentName)}`;
|
|
19
|
+
|
|
18
20
|
const metadata = {
|
|
19
21
|
metadataBase: publicUrl,
|
|
20
22
|
title,
|
|
@@ -24,6 +26,9 @@ export async function generateAgentMetadata({ params }: { params: Promise<{ agen
|
|
|
24
26
|
shortcut: iconUrl,
|
|
25
27
|
apple: iconUrl,
|
|
26
28
|
},
|
|
29
|
+
alternates: {
|
|
30
|
+
canonical: canonicalUrl,
|
|
31
|
+
},
|
|
27
32
|
openGraph: {
|
|
28
33
|
title,
|
|
29
34
|
description,
|
|
@@ -5,8 +5,8 @@ import { revalidatePath } from 'next/cache';
|
|
|
5
5
|
|
|
6
6
|
export async function restoreAgentVersion(agentName: string, historyId: number) {
|
|
7
7
|
const collection = await $provideAgentCollectionForServer();
|
|
8
|
-
await collection.
|
|
9
|
-
|
|
8
|
+
await collection.restoreAgentFromHistory(historyId);
|
|
9
|
+
|
|
10
10
|
revalidatePath(`/agents/${agentName}`);
|
|
11
11
|
revalidatePath(`/agents/${agentName}/history`);
|
|
12
12
|
}
|
|
@@ -10,7 +10,8 @@ export const metadata = {
|
|
|
10
10
|
export default async function AgentHistoryPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
11
11
|
const { agentName } = await params;
|
|
12
12
|
const collection = await $provideAgentCollectionForServer();
|
|
13
|
-
const
|
|
13
|
+
const agentId = await collection.getAgentPermanentId(agentName);
|
|
14
|
+
const history = await collection.listAgentHistory(agentId);
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
17
|
<div className="container mx-auto p-6 max-w-4xl">
|
|
@@ -21,7 +22,10 @@ export default async function AgentHistoryPage({ params }: { params: Promise<{ a
|
|
|
21
22
|
<div>
|
|
22
23
|
<h1 className="text-3xl font-bold text-gray-900">History: {agentName}</h1>
|
|
23
24
|
<p className="text-gray-600">
|
|
24
|
-
Previous versions of this agent.
|
|
25
|
+
Previous versions of this agent.{' '}
|
|
26
|
+
<Link href={`/agents/${agentName}`} className="text-blue-600 hover:underline">
|
|
27
|
+
Back to agent
|
|
28
|
+
</Link>
|
|
25
29
|
</p>
|
|
26
30
|
</div>
|
|
27
31
|
</header>
|
|
@@ -47,7 +51,10 @@ export default async function AgentHistoryPage({ params }: { params: Promise<{ a
|
|
|
47
51
|
Version {history.length - index}
|
|
48
52
|
</h3>
|
|
49
53
|
<p className="text-sm text-gray-500">
|
|
50
|
-
Hash:
|
|
54
|
+
Hash:{' '}
|
|
55
|
+
<code className="bg-gray-100 px-1 rounded">
|
|
56
|
+
{item.agentHash.substring(0, 8)}
|
|
57
|
+
</code>
|
|
51
58
|
</p>
|
|
52
59
|
</div>
|
|
53
60
|
<RestoreVersionButton agentName={agentName} historyId={item.id} />
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AgentBasicInformation } from '@promptbook-local/types';
|
|
2
|
+
import spaceTrim from 'spacetrim';
|
|
3
|
+
import { string_prompt_image } from '../../../../../../../../src/types/typeAliases';
|
|
4
|
+
|
|
5
|
+
export function getAgentDefaultAvatarPrompt(agent: AgentBasicInformation): string_prompt_image {
|
|
6
|
+
const {
|
|
7
|
+
agentName,
|
|
8
|
+
personaDescription,
|
|
9
|
+
meta: { fullname, color },
|
|
10
|
+
} = agent;
|
|
11
|
+
|
|
12
|
+
return spaceTrim(
|
|
13
|
+
(block) => `
|
|
14
|
+
Professional corporate headshot of ${fullname || agentName}
|
|
15
|
+
|
|
16
|
+
${block(personaDescription || '')}
|
|
17
|
+
|
|
18
|
+
- Professional business portrait photograph
|
|
19
|
+
- Photorealistic, studio quality lighting
|
|
20
|
+
- Shot with 85mm lens, shallow depth of field
|
|
21
|
+
- Neutral gray or soft gradient background
|
|
22
|
+
- Subject wearing professional attire with accent colors: ${color}
|
|
23
|
+
- Confident, approachable expression with slight smile
|
|
24
|
+
- Eye-level camera angle, centered composition
|
|
25
|
+
- Soft diffused lighting, subtle rim light
|
|
26
|
+
- Sharp focus on eyes, cinematic color grading
|
|
27
|
+
- 8K resolution, ultra detailed
|
|
28
|
+
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { $getTableName } from '@/src/database/$getTableName';
|
|
2
|
+
import { $provideSupabaseForServer } from '@/src/database/$provideSupabaseForServer';
|
|
3
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { $provideCdnForServer } from '@/src/tools/$provideCdnForServer';
|
|
5
|
+
import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
|
|
6
|
+
import { parseAgentSource } from '@promptbook-local/core';
|
|
7
|
+
import { computeHash, serializeError } from '@promptbook-local/utils';
|
|
8
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
9
|
+
import { assertsError } from '../../../../../../../../src/errors/assertsError';
|
|
10
|
+
import type { LlmExecutionTools } from '../../../../../../../../src/execution/LlmExecutionTools';
|
|
11
|
+
import { getSingleLlmExecutionTools } from '../../../../../../../../src/llm-providers/_multiple/getSingleLlmExecutionTools';
|
|
12
|
+
import type { string_url } from '../../../../../../../../src/types/typeAliases';
|
|
13
|
+
import { getAgentDefaultAvatarPrompt } from './getAgentDefaultAvatarPrompt';
|
|
14
|
+
|
|
15
|
+
export async function GET(request: NextRequest, { params }: { params: Promise<{ agentName: string }> }) {
|
|
16
|
+
try {
|
|
17
|
+
let { agentName } = await params;
|
|
18
|
+
agentName = decodeURIComponent(agentName);
|
|
19
|
+
|
|
20
|
+
if (!agentName) {
|
|
21
|
+
return NextResponse.json({ error: 'Agent name is required' }, { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 1. Fetch agent data first to construct the prompt
|
|
25
|
+
const collection = await $provideAgentCollectionForServer();
|
|
26
|
+
let agentSource;
|
|
27
|
+
try {
|
|
28
|
+
agentSource = await collection.getAgentSource(agentName);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
assertsError(error);
|
|
31
|
+
|
|
32
|
+
return NextResponse.json({ error: serializeError(error) }, { status: 500 });
|
|
33
|
+
|
|
34
|
+
//> // If agent not found, redirect to pravatar with the agent name as unique identifier
|
|
35
|
+
//> const pravaratUrl = `https://i.pravatar.cc/1024?u=${encodeURIComponent(agentName)}`;
|
|
36
|
+
//> return NextResponse.redirect(pravaratUrl);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const agentProfile = parseAgentSource(agentSource);
|
|
40
|
+
|
|
41
|
+
const prompt = getAgentDefaultAvatarPrompt(agentProfile);
|
|
42
|
+
|
|
43
|
+
// Use hash of the prompt as cache key - this ensures regeneration when prompt changes
|
|
44
|
+
const promptHash = computeHash(prompt);
|
|
45
|
+
const internalFilename = `agent-avatar-${promptHash}.png`;
|
|
46
|
+
|
|
47
|
+
const supabase = $provideSupabaseForServer();
|
|
48
|
+
const lockKey = `agent-avatar-${promptHash}`;
|
|
49
|
+
|
|
50
|
+
// Loop to acquire lock or wait for image
|
|
51
|
+
// We try for 60 seconds
|
|
52
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
53
|
+
// 1. Check if image with this prompt hash already exists in database
|
|
54
|
+
const { data: existingImage, error: selectError } = await supabase
|
|
55
|
+
.from(await $getTableName(`Image`))
|
|
56
|
+
.select('cdnUrl')
|
|
57
|
+
.eq('filename', internalFilename)
|
|
58
|
+
.single();
|
|
59
|
+
|
|
60
|
+
if (selectError && selectError.code !== 'PGRST116') {
|
|
61
|
+
// PGRST116 is "not found"
|
|
62
|
+
throw selectError;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (existingImage) {
|
|
66
|
+
// Image exists, fetch from CDN and return directly
|
|
67
|
+
const imageResponse = await fetch(existingImage.cdnUrl as string_url);
|
|
68
|
+
if (!imageResponse.ok) {
|
|
69
|
+
console.warn(`Failed to fetch image from CDN: ${imageResponse.status}`);
|
|
70
|
+
return NextResponse.redirect(existingImage.cdnUrl);
|
|
71
|
+
}
|
|
72
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
73
|
+
return new NextResponse(imageBuffer, {
|
|
74
|
+
status: 200,
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'image/png',
|
|
77
|
+
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Try to acquire lock to generate it
|
|
83
|
+
const { error: lockError } = await supabase.from(await $getTableName('GenerationLock')).insert({
|
|
84
|
+
lockKey,
|
|
85
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 5).toISOString(), // 5 minutes
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!lockError) {
|
|
89
|
+
// Lock acquired!
|
|
90
|
+
try {
|
|
91
|
+
// 2. Generate image
|
|
92
|
+
const executionTools = await $provideExecutionToolsForServer();
|
|
93
|
+
const llmTools = getSingleLlmExecutionTools(executionTools.llm) as LlmExecutionTools;
|
|
94
|
+
|
|
95
|
+
if (!llmTools.callImageGenerationModel) {
|
|
96
|
+
throw new Error('Image generation is not supported by the current LLM configuration');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const imageResult = await llmTools.callImageGenerationModel({
|
|
100
|
+
title: `Generate default avatar for ${agentName}`,
|
|
101
|
+
content: prompt,
|
|
102
|
+
parameters: {},
|
|
103
|
+
modelRequirements: {
|
|
104
|
+
modelVariant: 'IMAGE_GENERATION',
|
|
105
|
+
modelName: 'dall-e-3',
|
|
106
|
+
size: '1024x1792', // <- Vertical orientation
|
|
107
|
+
// <- TODO: [🤐] DRY
|
|
108
|
+
quality: 'hd',
|
|
109
|
+
style: 'natural',
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!imageResult.content) {
|
|
114
|
+
throw new Error('Failed to generate image: no content returned');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Download and Upload to CDN
|
|
118
|
+
const imageResponse = await fetch(imageResult.content);
|
|
119
|
+
if (!imageResponse.ok) {
|
|
120
|
+
throw new Error(`Failed to download generated image: ${imageResponse.status}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
124
|
+
const buffer = Buffer.from(imageBuffer);
|
|
125
|
+
|
|
126
|
+
const cdn = $provideCdnForServer();
|
|
127
|
+
const cdnKey = `generated-images/${internalFilename}`;
|
|
128
|
+
await cdn.setItem(cdnKey, {
|
|
129
|
+
type: 'image/png',
|
|
130
|
+
data: buffer,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const cdnUrl = cdn.getItemUrl(cdnKey);
|
|
134
|
+
|
|
135
|
+
// 4. Save to database
|
|
136
|
+
const { error: insertError } = await supabase.from(await $getTableName(`Image`)).insert({
|
|
137
|
+
filename: internalFilename,
|
|
138
|
+
prompt,
|
|
139
|
+
cdnUrl: cdnUrl.href,
|
|
140
|
+
cdnKey,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (insertError) {
|
|
144
|
+
throw insertError;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Return the newly created image directly
|
|
148
|
+
const finalImageResponse = await fetch(cdnUrl.href);
|
|
149
|
+
if (!finalImageResponse.ok) {
|
|
150
|
+
console.warn(`Failed to fetch newly created image from CDN: ${finalImageResponse.status}`);
|
|
151
|
+
return NextResponse.redirect(cdnUrl.href);
|
|
152
|
+
}
|
|
153
|
+
const finalImageBuffer = await finalImageResponse.arrayBuffer();
|
|
154
|
+
return new NextResponse(finalImageBuffer, {
|
|
155
|
+
status: 200,
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'image/png',
|
|
158
|
+
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
} finally {
|
|
162
|
+
// Release lock
|
|
163
|
+
await supabase.from(await $getTableName('GenerationLock')).delete().eq('lockKey', lockKey);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Lock failed, someone else is generating
|
|
168
|
+
// Check if expired
|
|
169
|
+
const { data: lockData } = await supabase
|
|
170
|
+
.from(await $getTableName('GenerationLock'))
|
|
171
|
+
.select('expiresAt')
|
|
172
|
+
.eq('lockKey', lockKey)
|
|
173
|
+
.single();
|
|
174
|
+
|
|
175
|
+
if (lockData && new Date(lockData.expiresAt) < new Date()) {
|
|
176
|
+
// Expired, delete and retry loop immediately
|
|
177
|
+
await supabase.from(await $getTableName('GenerationLock')).delete().eq('lockKey', lockKey);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Wait and retry
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw new Error('Timeout waiting for image generation');
|
|
186
|
+
} catch (error) {
|
|
187
|
+
assertsError(error);
|
|
188
|
+
console.error('Error serving default avatar:', error);
|
|
189
|
+
return new Response(JSON.stringify(serializeError(error), null, 4), {
|
|
190
|
+
status: 500,
|
|
191
|
+
headers: { 'Content-Type': 'application/json' },
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
2
|
+
import { generatePlaceholderAgentProfileImageUrl, PROMPTBOOK_COLOR } from '@promptbook-local/core';
|
|
2
3
|
import { serializeError } from '@promptbook-local/utils';
|
|
3
4
|
import { ImageResponse } from 'next/og';
|
|
4
5
|
import { assertsError } from '../../../../../../../../src/errors/assertsError';
|
|
@@ -18,6 +19,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
18
19
|
const agentName = await getAgentName(params);
|
|
19
20
|
const agentProfile = await getAgentProfile(agentName);
|
|
20
21
|
const agentColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
|
|
22
|
+
const { publicUrl } = await $provideServer();
|
|
21
23
|
|
|
22
24
|
return new ImageResponse(
|
|
23
25
|
(
|
|
@@ -30,6 +32,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
30
32
|
alignItems: 'center',
|
|
31
33
|
justifyContent: 'center',
|
|
32
34
|
borderRadius: '50%',
|
|
35
|
+
aspectRatio: '1 / 1',
|
|
33
36
|
overflow: 'hidden',
|
|
34
37
|
}}
|
|
35
38
|
>
|
|
@@ -45,7 +48,16 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
45
48
|
>
|
|
46
49
|
{/* Note: `next/image` is not working propperly with `next/og` */}
|
|
47
50
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
48
|
-
<img
|
|
51
|
+
<img
|
|
52
|
+
src={
|
|
53
|
+
agentProfile.meta.image ||
|
|
54
|
+
generatePlaceholderAgentProfileImageUrl(
|
|
55
|
+
agentProfile.permanentId || agentName,
|
|
56
|
+
publicUrl,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
alt="Agent Icon"
|
|
60
|
+
/>
|
|
49
61
|
</div>
|
|
50
62
|
</div>
|
|
51
63
|
),
|