@promptbook/cli 0.104.0-7 → 0.104.0-9
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/src/app/AddAgentButton.tsx +45 -19
- package/apps/agents-server/src/app/actions.ts +5 -0
- package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +108 -0
- package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +49 -10
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +2 -4
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
- package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +4 -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/route.ts +12 -5
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +10 -2
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +5 -5
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +5 -5
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +8 -2
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +8 -2
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +4 -3
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +3 -5
- package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +15 -4
- package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +3 -2
- package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +2 -1
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +2 -1
- 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/humans.txt/route.ts +1 -1
- package/apps/agents-server/src/app/page.tsx +4 -2
- package/apps/agents-server/src/app/recycle-bin/page.tsx +3 -1
- 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 +4 -5
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +25 -20
- package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +79 -0
- package/apps/agents-server/src/components/Header/Header.tsx +4 -0
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +46 -10
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +32 -14
- package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +22 -6
- 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/NewAgentDialog/NewAgentDialog.tsx +88 -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/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
- package/apps/agents-server/src/database/schema.ts +6 -3
- package/apps/agents-server/src/tools/$provideBrowserForServer.ts +29 -0
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
- package/esm/index.es.js +7 -2
- package/esm/index.es.js.map +1 -1
- 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 +2 -2
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +10 -6
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +6 -3
- package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +7 -2
- package/umd/index.umd.js.map +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
|
-
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
4
3
|
import { $provideServer } from '@/src/tools/$provideServer';
|
|
5
4
|
import { isUserAdmin } from '@/src/utils/isUserAdmin';
|
|
6
5
|
import { saturate } from '@promptbook-local/color';
|
|
@@ -28,6 +27,7 @@ export default async function AgentPage({
|
|
|
28
27
|
const isAdmin = await isUserAdmin();
|
|
29
28
|
const { headless: headlessParam } = await searchParams;
|
|
30
29
|
const isHeadless = headlessParam !== undefined;
|
|
30
|
+
const { publicUrl } = await $provideServer();
|
|
31
31
|
|
|
32
32
|
let agentProfile;
|
|
33
33
|
try {
|
|
@@ -45,8 +45,6 @@ export default async function AgentPage({
|
|
|
45
45
|
throw error;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const { publicUrl } = await $provideServer();
|
|
49
|
-
|
|
50
48
|
// Build agent page URL for QR and copy
|
|
51
49
|
const agentUrl = `${publicUrl.href}${encodeURIComponent(agentName)}`;
|
|
52
50
|
// <- TODO: [🐱🚀] Better
|
|
@@ -65,6 +63,7 @@ export default async function AgentPage({
|
|
|
65
63
|
<AgentProfileWrapper
|
|
66
64
|
agent={agentProfile}
|
|
67
65
|
agentUrl={agentUrl}
|
|
66
|
+
publicUrl={publicUrl}
|
|
68
67
|
agentEmail={agentEmail}
|
|
69
68
|
agentName={agentName}
|
|
70
69
|
brandColorHex={brandColorHex}
|
|
@@ -98,8 +97,7 @@ export default async function AgentPage({
|
|
|
98
97
|
brandColorHex={brandColorHex}
|
|
99
98
|
avatarSrc={
|
|
100
99
|
agentProfile.meta.image ||
|
|
101
|
-
agentProfile.permanentId ||
|
|
102
|
-
generatePlaceholderAgentProfileImageUrl(agentName, NEXT_PUBLIC_SITE_URL)
|
|
100
|
+
generatePlaceholderAgentProfileImageUrl(agentProfile.permanentId || agentName, publicUrl)
|
|
103
101
|
}
|
|
104
102
|
isDeleted={isDeleted}
|
|
105
103
|
/>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
5
|
+
import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
|
|
4
6
|
import { ArrowLeftIcon, FileTextIcon } from 'lucide-react';
|
|
5
7
|
import { headers } from 'next/headers';
|
|
6
8
|
import Link from 'next/link';
|
|
@@ -8,13 +10,14 @@ import { notFound } from 'next/navigation';
|
|
|
8
10
|
import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
|
|
9
11
|
import { getAgentName, getAgentProfile } from '../_utils';
|
|
10
12
|
import { generateAgentMetadata } from '../generateAgentMetadata';
|
|
11
|
-
import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
|
|
12
|
-
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
13
13
|
|
|
14
14
|
export const generateMetadata = generateAgentMetadata;
|
|
15
15
|
|
|
16
16
|
export default async function AgentSystemMessagePage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
17
17
|
$sideEffect(headers());
|
|
18
|
+
|
|
19
|
+
const { publicUrl } = await $provideServer();
|
|
20
|
+
|
|
18
21
|
const agentName = await getAgentName(params);
|
|
19
22
|
|
|
20
23
|
let agentProfile;
|
|
@@ -46,7 +49,13 @@ export default async function AgentSystemMessagePage({ params }: { params: Promi
|
|
|
46
49
|
{agentProfile.meta.image && (
|
|
47
50
|
// eslint-disable-next-line @next/next/no-img-element
|
|
48
51
|
<img
|
|
49
|
-
src={
|
|
52
|
+
src={
|
|
53
|
+
agentProfile.meta.image ||
|
|
54
|
+
generatePlaceholderAgentProfileImageUrl(
|
|
55
|
+
agentProfile.permanentId || agentName,
|
|
56
|
+
publicUrl,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
50
59
|
alt={agentProfile.meta.fullname || agentName}
|
|
51
60
|
className="w-16 h-16 rounded-full object-cover border-2 border-gray-200"
|
|
52
61
|
/>
|
|
@@ -78,7 +87,9 @@ export default async function AgentSystemMessagePage({ params }: { params: Promi
|
|
|
78
87
|
<div className="mt-6 bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
79
88
|
<h3 className="text-md font-semibold text-blue-900 mb-2">Model Requirements</h3>
|
|
80
89
|
<div className="text-sm text-blue-800">
|
|
81
|
-
<p
|
|
90
|
+
<p>
|
|
91
|
+
<strong>Model Variant:</strong> CHAT
|
|
92
|
+
</p>
|
|
82
93
|
{/* TODO: [🧠] Add more model requirements if available */}
|
|
83
94
|
</div>
|
|
84
95
|
</div>
|
|
@@ -8,7 +8,8 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
|
|
|
8
8
|
const collection = await $provideAgentCollectionForServer();
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
11
|
+
const agentId = await collection.getAgentPermanentId(agentName);
|
|
12
|
+
const source = await collection.getAgentSource(agentId);
|
|
12
13
|
|
|
13
14
|
// Generate new name
|
|
14
15
|
// TODO: [🧠] Better naming strategy, maybe check for collisions
|
|
@@ -18,7 +19,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
|
|
|
18
19
|
// eslint-disable-next-line no-constant-condition
|
|
19
20
|
while (true) {
|
|
20
21
|
try {
|
|
21
|
-
await collection.
|
|
22
|
+
await collection.getAgentPermanentId(newAgentName);
|
|
22
23
|
// If success, it means it exists, so we try next one
|
|
23
24
|
counter++;
|
|
24
25
|
newAgentName = `${agentName} (Copy ${counter})`;
|
|
@@ -8,7 +8,8 @@ export async function POST(request: Request, { params }: { params: Promise<{ age
|
|
|
8
8
|
const collection = await $provideAgentCollectionForServer();
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
|
-
await collection.
|
|
11
|
+
const agentId = await collection.getAgentPermanentId(agentName);
|
|
12
|
+
await collection.restoreAgent(agentId);
|
|
12
13
|
return NextResponse.json({ success: true });
|
|
13
14
|
} catch (error) {
|
|
14
15
|
return NextResponse.json(
|
|
@@ -49,7 +49,8 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ a
|
|
|
49
49
|
const collection = await $provideAgentCollectionForServer();
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
|
-
await collection.
|
|
52
|
+
const agentId = await collection.getAgentPermanentId(agentName);
|
|
53
|
+
await collection.deleteAgent(agentId);
|
|
53
54
|
return NextResponse.json({ success: true });
|
|
54
55
|
} catch (error) {
|
|
55
56
|
return NextResponse.json(
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
|
|
2
|
+
import { serializeError } from '@promptbook-local/utils';
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { assertsError } from '../../../../../../../src/errors/assertsError';
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
const browserContext = await $provideBrowserForServer();
|
|
9
|
+
|
|
10
|
+
const page = await browserContext.newPage();
|
|
11
|
+
|
|
12
|
+
await page.goto('https://ptbk.io');
|
|
13
|
+
const screenshotBuffer = await page.screenshot();
|
|
14
|
+
|
|
15
|
+
// await page.close();
|
|
16
|
+
// Do not close browser
|
|
17
|
+
// <- TODO: !!!! Fix
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
return new NextResponse(new Blob([screenshotBuffer as any]), {
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'image/png',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
assertsError(error);
|
|
27
|
+
console.error('Error taking screenshot:', error);
|
|
28
|
+
return NextResponse.json({ error: serializeError(error) }, { status: 500 });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { just } from '../../../../../../../src/utils/organization/just';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const stream = new ReadableStream({
|
|
10
|
+
async start(controller) {
|
|
11
|
+
try {
|
|
12
|
+
console.log('Starting Facebook scroll session');
|
|
13
|
+
const browserContext = await $provideBrowserForServer();
|
|
14
|
+
const page = await browserContext.newPage();
|
|
15
|
+
|
|
16
|
+
await page.goto('https://www.facebook.com/');
|
|
17
|
+
console.log('Navigated to Facebook');
|
|
18
|
+
|
|
19
|
+
// Loop for streaming
|
|
20
|
+
while (just(true)) {
|
|
21
|
+
// Check for login (simple check for now)
|
|
22
|
+
// Facebook uses role="banner" for the top navigation bar when logged in
|
|
23
|
+
const isLoggedIn = await page.$('div[role="banner"]');
|
|
24
|
+
|
|
25
|
+
if (isLoggedIn) {
|
|
26
|
+
// Scroll down
|
|
27
|
+
await page.evaluate(() => window.scrollBy(0, 500));
|
|
28
|
+
// console.log('Scrolled');
|
|
29
|
+
} else {
|
|
30
|
+
// console.log('Waiting for login...');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const buffer = await page.screenshot({ type: 'jpeg', quality: 50 });
|
|
34
|
+
|
|
35
|
+
const boundary = '--myboundary';
|
|
36
|
+
const header = `\r\n${boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: ${buffer.length}\r\n\r\n`;
|
|
37
|
+
|
|
38
|
+
controller.enqueue(encoder.encode(header));
|
|
39
|
+
controller.enqueue(buffer);
|
|
40
|
+
|
|
41
|
+
// Short delay
|
|
42
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Stream error:', error);
|
|
46
|
+
try {
|
|
47
|
+
controller.close();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// Ignore error if controller is already closed
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return new NextResponse(stream, {
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'multipart/x-mixed-replace; boundary=myboundary',
|
|
58
|
+
'Cache-Control': 'no-cache',
|
|
59
|
+
Connection: 'keep-alive',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
|
|
|
6
6
|
export const dynamic = 'force-dynamic';
|
|
7
7
|
|
|
8
8
|
export async function GET() {
|
|
9
|
-
const txt = generateHumansTxt();
|
|
9
|
+
const txt = await generateHumansTxt();
|
|
10
10
|
return new NextResponse(txt, {
|
|
11
11
|
headers: {
|
|
12
12
|
'Content-Type': 'text/plain',
|
|
@@ -34,6 +34,8 @@ const calendarWithSeconds = {
|
|
|
34
34
|
export default async function HomePage() {
|
|
35
35
|
$sideEffect(/* Note: [🐶] This will ensure dynamic rendering of page and avoid Next.js pre-render */ headers());
|
|
36
36
|
|
|
37
|
+
const { publicUrl } = await $provideServer();
|
|
38
|
+
|
|
37
39
|
const currentUser = await getCurrentUser();
|
|
38
40
|
const isAdmin = await isUserAdmin(); /* <- TODO: [👹] Here should be user permissions */
|
|
39
41
|
|
|
@@ -94,9 +96,9 @@ export default async function HomePage() {
|
|
|
94
96
|
return (
|
|
95
97
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
96
98
|
<div className="container mx-auto px-4 py-16">
|
|
97
|
-
<AgentsList agents={[...agents]} isAdmin={isAdmin} />
|
|
99
|
+
<AgentsList agents={[...agents]} isAdmin={isAdmin} publicUrl={publicUrl} />
|
|
98
100
|
|
|
99
|
-
<ExternalAgentsSectionClient />
|
|
101
|
+
<ExternalAgentsSectionClient publicUrl={publicUrl} />
|
|
100
102
|
|
|
101
103
|
{isAdmin && <UsersList allowCreate={false} />}
|
|
102
104
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
2
3
|
import { TrashIcon } from 'lucide-react';
|
|
3
4
|
import Link from 'next/link';
|
|
4
5
|
import { DeletedAgentsList } from '../../components/Homepage/DeletedAgentsList';
|
|
@@ -9,6 +10,7 @@ export const metadata = {
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
export default async function RecycleBinPage() {
|
|
13
|
+
const { publicUrl } = await $provideServer();
|
|
12
14
|
const collection = await $provideAgentCollectionForServer();
|
|
13
15
|
const deletedAgents = await collection.listDeletedAgents();
|
|
14
16
|
const isAdmin = await isUserAdmin();
|
|
@@ -34,7 +36,7 @@ export default async function RecycleBinPage() {
|
|
|
34
36
|
</Link>
|
|
35
37
|
</div>
|
|
36
38
|
) : (
|
|
37
|
-
<DeletedAgentsList agents={deletedAgents} isAdmin={isAdmin} />
|
|
39
|
+
<DeletedAgentsList agents={deletedAgents} isAdmin={isAdmin} publicUrl={publicUrl} />
|
|
38
40
|
)}
|
|
39
41
|
</div>
|
|
40
42
|
</div>
|
|
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
|
|
|
6
6
|
export const dynamic = 'force-dynamic';
|
|
7
7
|
|
|
8
8
|
export async function GET() {
|
|
9
|
-
const txt = generateRobotsTxt();
|
|
9
|
+
const txt = await generateRobotsTxt();
|
|
10
10
|
return new NextResponse(txt, {
|
|
11
11
|
headers: {
|
|
12
12
|
'Content-Type': 'text/plain',
|
|
@@ -6,7 +6,7 @@ import { NextResponse } from 'next/server';
|
|
|
6
6
|
export const dynamic = 'force-dynamic';
|
|
7
7
|
|
|
8
8
|
export async function GET() {
|
|
9
|
-
const txt = generateSecurityTxt();
|
|
9
|
+
const txt = await generateSecurityTxt();
|
|
10
10
|
return new NextResponse(txt, {
|
|
11
11
|
headers: {
|
|
12
12
|
'Content-Type': 'text/plain',
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
// Dynamic sitemap.xml for Agents Server
|
|
2
2
|
|
|
3
|
-
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
4
3
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
5
5
|
import { spaceTrim } from '@promptbook-local/utils';
|
|
6
6
|
import { NextResponse } from 'next/server';
|
|
7
7
|
|
|
8
8
|
export const dynamic = 'force-dynamic';
|
|
9
9
|
|
|
10
10
|
export async function GET() {
|
|
11
|
+
const { publicUrl } = await $provideServer();
|
|
12
|
+
|
|
11
13
|
const collection = await $provideAgentCollectionForServer();
|
|
12
14
|
|
|
13
15
|
// Assume collection.listAgents() returns an array of agent names
|
|
14
16
|
const agents = await collection.listAgents();
|
|
15
17
|
|
|
16
|
-
// Get base URL from environment or config
|
|
17
|
-
const baseUrl = NEXT_PUBLIC_SITE_URL?.href || process.env.PUBLIC_URL || 'https://ptbk.io';
|
|
18
|
-
|
|
19
18
|
const urls = agents
|
|
20
19
|
.map(
|
|
21
20
|
({ permanentId, agentName }) =>
|
|
22
|
-
`<url><loc>${
|
|
21
|
+
`<url><loc>${publicUrl.href}agents/${encodeURIComponent(permanentId || agentName)}</loc></url>`,
|
|
23
22
|
)
|
|
24
23
|
.join('\n');
|
|
25
24
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { colorToDataUrl } from '@promptbook-local/color';
|
|
3
4
|
import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
|
|
4
5
|
import { AgentBasicInformation, string_agent_permanent_id } from '@promptbook-local/types';
|
|
5
6
|
import { RepeatIcon } from 'lucide-react';
|
|
6
7
|
import { useState } from 'react';
|
|
8
|
+
import { AgentProfileImage } from './AgentProfileImage';
|
|
7
9
|
import { AgentQrCode } from './AgentQrCode';
|
|
8
10
|
import { QrCodeModal } from './QrCodeModal';
|
|
9
11
|
import { useAgentBackground } from './useAgentBackground';
|
|
10
|
-
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
11
12
|
|
|
12
13
|
type AgentProfileProps = {
|
|
13
14
|
/**
|
|
@@ -27,6 +28,11 @@ type AgentProfileProps = {
|
|
|
27
28
|
*/
|
|
28
29
|
readonly agentUrl?: string;
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Base URL of the agents server
|
|
33
|
+
*/
|
|
34
|
+
readonly publicUrl: URL;
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
37
|
* Email of the agent
|
|
32
38
|
*/
|
|
@@ -65,6 +71,7 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
65
71
|
agent,
|
|
66
72
|
agentUrl = '',
|
|
67
73
|
agentEmail = '',
|
|
74
|
+
publicUrl,
|
|
68
75
|
permanentId,
|
|
69
76
|
renderMenu,
|
|
70
77
|
children,
|
|
@@ -72,11 +79,11 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
72
79
|
isHeadless = false,
|
|
73
80
|
className,
|
|
74
81
|
} = props;
|
|
75
|
-
|
|
82
|
+
|
|
76
83
|
const { meta, agentName } = agent;
|
|
77
84
|
const fullname = (meta.fullname as string) || agentName || 'Agent';
|
|
78
85
|
const personaDescription = agent.personaDescription || '';
|
|
79
|
-
const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(permanentId,
|
|
86
|
+
const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(permanentId, publicUrl);
|
|
80
87
|
|
|
81
88
|
const [isQrModalOpen, setIsQrModalOpen] = useState(false);
|
|
82
89
|
const [isFlipped, setIsFlipped] = useState(false);
|
|
@@ -126,14 +133,14 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
126
133
|
)}
|
|
127
134
|
|
|
128
135
|
{/* Main profile content */}
|
|
129
|
-
<div className="relative z-10 grid grid-cols-[auto_1fr] gap-
|
|
136
|
+
<div className="relative z-10 flex flex-col md:grid md:grid-cols-[auto_1fr] gap-y-6 md:gap-y-4 md:gap-x-12 max-w-5xl w-full items-center md:items-start">
|
|
130
137
|
{/* Agent image card (Flippable) */}
|
|
131
138
|
<div
|
|
132
|
-
className="flex-shrink-0 perspective-1000 group row-start-1 col-start-1 md:row-span-3"
|
|
139
|
+
className="flex-shrink-0 perspective-1000 group w-full md:w-auto md:row-start-1 md:col-start-1 md:row-span-3"
|
|
133
140
|
style={{ perspective: '1000px' }}
|
|
134
141
|
>
|
|
135
142
|
<div
|
|
136
|
-
className="relative w-
|
|
143
|
+
className="relative w-full md:w-80 transition-all duration-700 transform-style-3d cursor-pointer max-w-sm mx-auto md:max-w-none md:mx-0"
|
|
137
144
|
style={{
|
|
138
145
|
aspectRatio: '1 / 1.618', // Golden Ratio
|
|
139
146
|
transformStyle: 'preserve-3d',
|
|
@@ -154,17 +161,15 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
154
161
|
// ['cornerShape' as really_any /* <- Note: `cornerShape` is non standard CSS property */]: 'squircle ',
|
|
155
162
|
}}
|
|
156
163
|
>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
164
|
+
<AgentProfileImage
|
|
165
|
+
src={imageUrl}
|
|
166
|
+
alt={fullname}
|
|
167
|
+
className="w-full h-full object-cover"
|
|
168
|
+
style={{
|
|
169
|
+
objectFit: 'cover',
|
|
170
|
+
backgroundImage: `url(${colorToDataUrl(brandColorLightHex)})`,
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
168
173
|
|
|
169
174
|
{/* Flip hint icon */}
|
|
170
175
|
<div className="absolute bottom-2 md:bottom-4 right-2 md:right-4 bg-black/30 p-1 md:p-2 rounded-full text-white/80 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity">
|
|
@@ -202,7 +207,7 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
202
207
|
</div>
|
|
203
208
|
|
|
204
209
|
{/* Agent info - Header Area */}
|
|
205
|
-
<div className="row-start-1 col-start-2 flex flex-col justify-center md:justify-start
|
|
210
|
+
<div className="w-full md:w-auto md:row-start-1 md:col-start-2 flex flex-col justify-center md:justify-start items-center md:items-start h-auto gap-4 md:gap-6 text-center md:text-left">
|
|
206
211
|
{/* Agent name with custom font */}
|
|
207
212
|
<h1
|
|
208
213
|
className="text-2xl md:text-5xl lg:text-6xl font-bold text-gray-900 tracking-tight leading-tight"
|
|
@@ -220,11 +225,11 @@ export function AgentProfile(props: AgentProfileProps) {
|
|
|
220
225
|
</div>
|
|
221
226
|
|
|
222
227
|
{/* Chat Area */}
|
|
223
|
-
<div className="
|
|
228
|
+
<div className="w-full md:col-span-1 md:col-start-2 mt-4 md:mt-0">{children}</div>
|
|
224
229
|
|
|
225
230
|
{/* Secondary Actions */}
|
|
226
231
|
{!isHeadless && (
|
|
227
|
-
<div className="
|
|
232
|
+
<div className="w-full md:col-span-1 md:col-start-2 flex flex-wrap justify-center md:justify-start items-center gap-4 md:gap-6 mt-4 md:mt-2">
|
|
228
233
|
{actions}
|
|
229
234
|
</div>
|
|
230
235
|
)}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Loader2 } from 'lucide-react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
type AgentProfileImageProps = {
|
|
5
|
+
readonly src: string;
|
|
6
|
+
readonly alt: string;
|
|
7
|
+
readonly className?: string;
|
|
8
|
+
readonly style?: React.CSSProperties;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function AgentProfileImage({ src, alt, className, style }: AgentProfileImageProps) {
|
|
12
|
+
const [imageSrc, setImageSrc] = useState<string | null>(null);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
let isMounted = true;
|
|
17
|
+
let timeoutId: NodeJS.Timeout;
|
|
18
|
+
let objectUrl: string | null = null;
|
|
19
|
+
|
|
20
|
+
const fetchImage = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(src);
|
|
23
|
+
if (response.ok) {
|
|
24
|
+
const blob = await response.blob();
|
|
25
|
+
if (!isMounted) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
objectUrl = URL.createObjectURL(blob);
|
|
30
|
+
setImageSrc(objectUrl);
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
} else {
|
|
33
|
+
// Retry on non-200
|
|
34
|
+
if (isMounted) {
|
|
35
|
+
timeoutId = setTimeout(fetchImage, 2000);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.warn('Failed to fetch image, retrying...', error);
|
|
40
|
+
if (isMounted) {
|
|
41
|
+
timeoutId = setTimeout(fetchImage, 2000);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
setIsLoading(true);
|
|
47
|
+
fetchImage();
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
isMounted = false;
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
if (objectUrl) {
|
|
53
|
+
URL.revokeObjectURL(objectUrl);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, [src]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className={`relative ${className}`} style={style}>
|
|
60
|
+
{/* Note: We apply style to the container, so backgroundImage works here */}
|
|
61
|
+
|
|
62
|
+
{isLoading && (
|
|
63
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
64
|
+
<Loader2 className="w-8 h-8 animate-spin text-white/50" />
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{imageSrc && (
|
|
69
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
70
|
+
<img
|
|
71
|
+
src={imageSrc}
|
|
72
|
+
alt={alt}
|
|
73
|
+
className="w-full h-full object-cover"
|
|
74
|
+
// We don't pass style here because it is applied to container
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
4
3
|
import { generatePlaceholderAgentProfileImageUrl } from '@promptbook-local/core';
|
|
5
4
|
import { really_any } from '@promptbook-local/types';
|
|
6
5
|
import { EyeIcon, EyeOffIcon, RotateCcwIcon } from 'lucide-react';
|
|
@@ -9,14 +8,50 @@ import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/
|
|
|
9
8
|
import { useAgentBackground } from '../AgentProfile/useAgentBackground';
|
|
10
9
|
|
|
11
10
|
type AgentCardProps = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @@@
|
|
13
|
+
*/
|
|
14
|
+
readonly agent: AgentBasicInformation;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @@@
|
|
18
|
+
*/
|
|
19
|
+
readonly href: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base URL of the agents server
|
|
23
|
+
*/
|
|
24
|
+
readonly publicUrl: URL;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @@@
|
|
28
|
+
*/
|
|
29
|
+
readonly isAdmin?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @@@
|
|
33
|
+
*/
|
|
34
|
+
readonly onDelete?: (agentIdentifier: string) => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @@@
|
|
38
|
+
*/
|
|
39
|
+
readonly onClone?: (agentIdentifier: string) => void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @@@
|
|
43
|
+
*/
|
|
44
|
+
readonly onToggleVisibility?: (agentIdentifier: string) => void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @@@
|
|
48
|
+
*/
|
|
49
|
+
readonly onRestore?: (agentIdentifier: string) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @@@
|
|
53
|
+
*/
|
|
54
|
+
readonly visibility?: 'PUBLIC' | 'PRIVATE';
|
|
20
55
|
};
|
|
21
56
|
|
|
22
57
|
const ACTION_BUTTON_CLASSES =
|
|
@@ -26,6 +61,7 @@ export function AgentCard({
|
|
|
26
61
|
agent,
|
|
27
62
|
href,
|
|
28
63
|
isAdmin,
|
|
64
|
+
publicUrl,
|
|
29
65
|
onDelete,
|
|
30
66
|
onClone,
|
|
31
67
|
onToggleVisibility,
|
|
@@ -34,7 +70,7 @@ export function AgentCard({
|
|
|
34
70
|
}: AgentCardProps) {
|
|
35
71
|
const { meta, agentName } = agent;
|
|
36
72
|
const fullname = (meta.fullname as string) || agentName || 'Agent';
|
|
37
|
-
const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(agentName,
|
|
73
|
+
const imageUrl = meta.image || generatePlaceholderAgentProfileImageUrl(agentName, publicUrl);
|
|
38
74
|
const personaDescription = agent.personaDescription || '';
|
|
39
75
|
|
|
40
76
|
const { brandColorLightHex, brandColorDarkHex, backgroundImage } = useAgentBackground(meta.color);
|