@promptbook/cli 0.103.0-67 → 0.103.0-68
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/TODO.txt +1 -5
- package/apps/agents-server/package-lock.json +8 -2317
- package/apps/agents-server/package.json +0 -9
- package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +34 -2
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +4 -0
- package/apps/agents-server/src/app/humans.txt/route.ts +15 -0
- package/apps/agents-server/src/app/layout.tsx +31 -0
- package/apps/agents-server/src/app/robots.txt/route.ts +15 -0
- package/apps/agents-server/src/app/security.txt/route.ts +15 -0
- package/apps/agents-server/src/app/sitemap.xml/route.ts +37 -0
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +19 -18
- package/apps/agents-server/src/components/Footer/Footer.tsx +13 -13
- package/apps/agents-server/src/components/Header/Header.tsx +95 -20
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -0
- package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +28 -0
- package/apps/agents-server/src/components/_utils/headlessParam.tsx +36 -0
- package/apps/agents-server/src/middleware.ts +6 -2
- package/esm/index.es.js +191 -14
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/_packages/components.index.d.ts +2 -0
- package/esm/typings/src/_packages/types.index.d.ts +6 -0
- package/esm/typings/src/book-components/BookEditor/BookEditor.d.ts +10 -0
- package/esm/typings/src/book-components/BookEditor/BookEditorActionbar.d.ts +4 -0
- package/esm/typings/src/book-components/icons/CameraIcon.d.ts +11 -0
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +5 -1
- package/esm/typings/src/execution/PromptResult.d.ts +7 -1
- package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +4 -0
- package/esm/typings/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +13 -1
- package/esm/typings/src/llm-providers/openai/OpenAiExecutionTools.d.ts +4 -0
- package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +6 -6
- package/esm/typings/src/types/ModelRequirements.d.ts +13 -1
- package/esm/typings/src/types/ModelVariant.d.ts +1 -1
- package/esm/typings/src/types/Prompt.d.ts +13 -1
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +191 -14
- package/umd/index.umd.js.map +1 -1
|
@@ -8,14 +8,5 @@
|
|
|
8
8
|
"start": "next start -p 4440",
|
|
9
9
|
"lint": "next lint",
|
|
10
10
|
"postinstall": "cd ../../ && npm ci"
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"@tailwindcss/typography": "^0.5.19",
|
|
14
|
-
"autoprefixer": "^10.4.21",
|
|
15
|
-
"postcss": "^8.5.6",
|
|
16
|
-
"tailwindcss": "^3.4.18"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"remark-gfm": "^4.0.1"
|
|
20
11
|
}
|
|
21
12
|
}
|
|
@@ -32,6 +32,7 @@ const barlowCondensed = Barlow_Condensed({
|
|
|
32
32
|
|
|
33
33
|
type AgentOptionsMenuProps = {
|
|
34
34
|
agentName: string;
|
|
35
|
+
derivedAgentName: string;
|
|
35
36
|
agentUrl: string;
|
|
36
37
|
agentEmail: string;
|
|
37
38
|
brandColorHex: string;
|
|
@@ -42,6 +43,7 @@ type AgentOptionsMenuProps = {
|
|
|
42
43
|
|
|
43
44
|
export function AgentOptionsMenu({
|
|
44
45
|
agentName,
|
|
46
|
+
derivedAgentName,
|
|
45
47
|
agentUrl,
|
|
46
48
|
agentEmail,
|
|
47
49
|
brandColorHex,
|
|
@@ -119,7 +121,33 @@ export function AgentOptionsMenu({
|
|
|
119
121
|
const historyLink = links.find((l) => l.title === 'History & Feedback')!;
|
|
120
122
|
const allLinksLink = links.find((l) => l.title === 'All Links')!;
|
|
121
123
|
|
|
124
|
+
// "Update URL" logic
|
|
125
|
+
const showUpdateUrl = agentName !== derivedAgentName;
|
|
126
|
+
const updateUrlHref = `/agents/${encodeURIComponent(derivedAgentName)}`;
|
|
127
|
+
|
|
128
|
+
const handleUpdateUrl = () => {
|
|
129
|
+
if (
|
|
130
|
+
window.confirm(
|
|
131
|
+
`Are you sure you want to change the agent URL from "/agents/${agentName}" to "/agents/${derivedAgentName}"?`
|
|
132
|
+
)
|
|
133
|
+
) {
|
|
134
|
+
window.location.href = updateUrlHref;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
122
138
|
const menuItems = [
|
|
139
|
+
...(showUpdateUrl
|
|
140
|
+
? [
|
|
141
|
+
{
|
|
142
|
+
type: 'action' as const,
|
|
143
|
+
icon: MoreHorizontalIcon,
|
|
144
|
+
label: 'Update URL',
|
|
145
|
+
onClick: handleUpdateUrl,
|
|
146
|
+
highlight: true,
|
|
147
|
+
},
|
|
148
|
+
{ type: 'divider' as const },
|
|
149
|
+
]
|
|
150
|
+
: []),
|
|
123
151
|
{
|
|
124
152
|
type: 'link' as const,
|
|
125
153
|
href: `/agents/${encodeURIComponent(agentName)}/chat`,
|
|
@@ -268,9 +296,13 @@ export function AgentOptionsMenu({
|
|
|
268
296
|
// Keep menu open for copy feedback
|
|
269
297
|
}
|
|
270
298
|
}}
|
|
271
|
-
className=
|
|
299
|
+
className={`flex items-center gap-3 px-4 py-2.5 w-full text-left transition-colors
|
|
300
|
+
${item.highlight
|
|
301
|
+
? 'bg-yellow-100 text-yellow-900 font-bold hover:bg-yellow-200'
|
|
302
|
+
: 'text-gray-700 hover:bg-gray-50'}
|
|
303
|
+
`}
|
|
272
304
|
>
|
|
273
|
-
<item.icon className=
|
|
305
|
+
<item.icon className={`w-4 h-4 ${item.highlight ? 'text-yellow-700' : 'text-gray-500'}`} />
|
|
274
306
|
<span className="text-sm font-medium">{item.label}</span>
|
|
275
307
|
</button>
|
|
276
308
|
);
|
|
@@ -19,6 +19,9 @@ type AgentProfileWrapperProps = {
|
|
|
19
19
|
export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
|
|
20
20
|
const { agent, agentUrl, agentEmail, agentName, brandColorHex, isAdmin, isHeadless, actions, children } = props;
|
|
21
21
|
|
|
22
|
+
// Derived agentName from agent data
|
|
23
|
+
const derivedAgentName = agent.agentName;
|
|
24
|
+
|
|
22
25
|
return (
|
|
23
26
|
<AgentProfile
|
|
24
27
|
agent={agent}
|
|
@@ -28,6 +31,7 @@ export function AgentProfileWrapper(props: AgentProfileWrapperProps) {
|
|
|
28
31
|
renderMenu={({ onShowQrCode }) => (
|
|
29
32
|
<AgentOptionsMenu
|
|
30
33
|
agentName={agentName}
|
|
34
|
+
derivedAgentName={derivedAgentName}
|
|
31
35
|
agentUrl={agentUrl}
|
|
32
36
|
agentEmail={agentEmail}
|
|
33
37
|
brandColorHex={brandColorHex}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Dynamic humans.txt for Agents Server
|
|
2
|
+
|
|
3
|
+
import { generateHumansTxt } from '@/src/components/_utils/generateMetaTxt';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const txt = generateHumansTxt();
|
|
10
|
+
return new NextResponse(txt, {
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'text/plain',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -7,6 +7,7 @@ import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollecti
|
|
|
7
7
|
import { $provideServer } from '../tools/$provideServer';
|
|
8
8
|
import { getCurrentUser } from '../utils/getCurrentUser';
|
|
9
9
|
import { isUserAdmin } from '../utils/isUserAdmin';
|
|
10
|
+
import { getFederatedServersFromMetadata } from '../utils/getFederatedServersFromMetadata';
|
|
10
11
|
import './globals.css';
|
|
11
12
|
|
|
12
13
|
const barlowCondensed = Barlow_Condensed({
|
|
@@ -82,6 +83,35 @@ export default async function RootLayout({
|
|
|
82
83
|
console.error('Failed to parse FOOTER_LINKS', error);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// Fetch federated servers and add to footerLinks
|
|
87
|
+
let federatedServers: Array<{ url: string; title: string; logoUrl: string | null }> = [];
|
|
88
|
+
try {
|
|
89
|
+
const federatedServersRaw = await getFederatedServersFromMetadata();
|
|
90
|
+
federatedServers = await Promise.all(
|
|
91
|
+
federatedServersRaw.map(async (url: string) => {
|
|
92
|
+
let logoUrl: string | null = null;
|
|
93
|
+
try {
|
|
94
|
+
// Try to fetch logo from metadata endpoint if available
|
|
95
|
+
const res = await fetch(`${url}/api/metadata`);
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
const meta = await res.json();
|
|
98
|
+
logoUrl = meta.SERVER_LOGO_URL || null;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
logoUrl = null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
title: `Federated: ${new URL(url).hostname}`,
|
|
105
|
+
url,
|
|
106
|
+
logoUrl,
|
|
107
|
+
};
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
footerLinks = [...footerLinks, ...federatedServers];
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Failed to fetch federated servers for footer', error);
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
const collection = await $provideAgentCollectionForServer();
|
|
86
116
|
const agents = await collection.listAgents();
|
|
87
117
|
|
|
@@ -97,6 +127,7 @@ export default async function RootLayout({
|
|
|
97
127
|
agents={JSON.parse(JSON.stringify(agents))}
|
|
98
128
|
isFooterShown={isFooterShown}
|
|
99
129
|
footerLinks={footerLinks}
|
|
130
|
+
federatedServers={federatedServers}
|
|
100
131
|
>
|
|
101
132
|
{children}
|
|
102
133
|
</LayoutWrapper>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Dynamic robots.txt for Agents Server
|
|
2
|
+
|
|
3
|
+
import { generateRobotsTxt } from '@/src/components/_utils/generateMetaTxt';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const txt = generateRobotsTxt();
|
|
10
|
+
return new NextResponse(txt, {
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'text/plain',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Dynamic security.txt for Agents Server
|
|
2
|
+
|
|
3
|
+
import { generateSecurityTxt } from '@/src/components/_utils/generateMetaTxt';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const txt = generateSecurityTxt();
|
|
10
|
+
return new NextResponse(txt, {
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'text/plain',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Dynamic sitemap.xml for Agents Server
|
|
2
|
+
|
|
3
|
+
import { NEXT_PUBLIC_SITE_URL } from '@/config';
|
|
4
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
5
|
+
import { spaceTrim } from '@promptbook-local/utils';
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
|
|
8
|
+
export const dynamic = 'force-dynamic';
|
|
9
|
+
|
|
10
|
+
export async function GET() {
|
|
11
|
+
const collection = await $provideAgentCollectionForServer();
|
|
12
|
+
|
|
13
|
+
// Assume collection.listAgents() returns an array of agent names
|
|
14
|
+
const agentNames = await collection.listAgents();
|
|
15
|
+
|
|
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
|
+
const urls = agentNames
|
|
20
|
+
.map(({ agentName }) => `<url><loc>${baseUrl}agents/${encodeURIComponent(agentName)}</loc></url>`)
|
|
21
|
+
.join('\n');
|
|
22
|
+
|
|
23
|
+
const xml = spaceTrim(
|
|
24
|
+
(block) => `
|
|
25
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
26
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
27
|
+
${block(urls)}
|
|
28
|
+
</urlset>
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return new NextResponse(xml, {
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/xml',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import remarkGfm from 'remark-gfm';
|
|
3
|
-
import { string_book } from '../../../../../src/book-2.0/agent-source/string_book';
|
|
4
|
-
import { BookEditor } from '../../../../../src/book-components/BookEditor/BookEditor';
|
|
1
|
+
import { MarkdownContent } from '@promptbook-local/components';
|
|
5
2
|
import { OpenMojiIcon } from '../OpenMojiIcon/OpenMojiIcon';
|
|
6
3
|
|
|
7
4
|
type DocumentationContentProps = {
|
|
@@ -17,16 +14,22 @@ type DocumentationContentProps = {
|
|
|
17
14
|
|
|
18
15
|
export function DocumentationContent({ primary, aliases = [], isPrintOnly = false }: DocumentationContentProps) {
|
|
19
16
|
return (
|
|
20
|
-
<div
|
|
21
|
-
|
|
17
|
+
<div
|
|
18
|
+
className={`bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden ${
|
|
19
|
+
isPrintOnly ? 'shadow-none border-none' : ''
|
|
20
|
+
}`}
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
className={`p-8 border-b border-gray-100 bg-gray-50/50 ${
|
|
24
|
+
isPrintOnly ? 'border-none bg-white p-0 mb-4' : ''
|
|
25
|
+
}`}
|
|
26
|
+
>
|
|
22
27
|
<div className="flex items-center gap-4 mb-4">
|
|
23
28
|
<h1 className="text-4xl font-bold text-gray-900 tracking-tight">
|
|
24
29
|
<OpenMojiIcon icon={primary.icon} className="mr-3" />
|
|
25
30
|
{primary.type}
|
|
26
31
|
{aliases.length > 0 && (
|
|
27
|
-
<span className="text-gray-400 font-normal ml-4 text-2xl">
|
|
28
|
-
/ {aliases.join(' / ')}
|
|
29
|
-
</span>
|
|
32
|
+
<span className="text-gray-400 font-normal ml-4 text-2xl">/ {aliases.join(' / ')}</span>
|
|
30
33
|
)}
|
|
31
34
|
</h1>
|
|
32
35
|
{!isPrintOnly && (
|
|
@@ -36,16 +39,15 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
|
|
|
36
39
|
)}
|
|
37
40
|
</div>
|
|
38
41
|
{primary.description && (
|
|
39
|
-
<p className="text-xl text-gray-600 leading-relaxed max-w-3xl">
|
|
40
|
-
{primary.description}
|
|
41
|
-
</p>
|
|
42
|
+
<p className="text-xl text-gray-600 leading-relaxed max-w-3xl">{primary.description}</p>
|
|
42
43
|
)}
|
|
43
44
|
</div>
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
<div className={`p-8 ${isPrintOnly ? 'p-0' : ''}`}>
|
|
46
47
|
<article className="prose prose-lg prose-slate max-w-none prose-headings:font-bold prose-headings:tracking-tight prose-headings:text-gray-900 prose-h1:text-4xl prose-h1:mb-8 prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:pb-2 prose-h2:border-b prose-h2:border-gray-200 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 prose-h3:text-gray-800 prose-p:text-gray-600 prose-p:leading-relaxed prose-p:mb-6 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:text-blue-700 hover:prose-a:underline prose-a:transition-colors prose-strong:font-bold prose-strong:text-gray-900 prose-code:text-blue-600 prose-code:bg-blue-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none prose-code:font-medium prose-pre:bg-gray-900 prose-pre:text-gray-100 prose-pre:shadow-lg prose-pre:rounded-xl prose-pre:p-6 prose-ul:list-disc prose-ul:pl-6 prose-li:marker:text-gray-400 prose-li:mb-2 prose-ol:list-decimal prose-ol:pl-6 prose-li:mb-2 prose-blockquote:border-l-4 prose-blockquote:border-blue-500 prose-blockquote:bg-blue-50/50 prose-blockquote:py-2 prose-blockquote:px-4 prose-blockquote:rounded-r-lg prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-blockquote:my-8 prose-img:rounded-xl prose-img:shadow-md prose-img:my-8 prose-hr:border-gray-200 prose-hr:my-10 prose-table:w-full prose-th:text-left prose-th:py-2 prose-th:px-3 prose-th:bg-gray-100 prose-th:font-semibold prose-th:text-gray-900 prose-td:py-2 prose-td:px-3 prose-td:border-b prose-td:border-gray-200 prose-tr:hover:bg-gray-50">
|
|
47
|
-
<
|
|
48
|
-
|
|
48
|
+
<MarkdownContent
|
|
49
|
+
content={primary.documentation}
|
|
50
|
+
/* TODO: !!!!
|
|
49
51
|
components={{
|
|
50
52
|
code(props) {
|
|
51
53
|
const { children, className, node, ...rest } = props;
|
|
@@ -77,9 +79,8 @@ export function DocumentationContent({ primary, aliases = [], isPrintOnly = fals
|
|
|
77
79
|
);
|
|
78
80
|
},
|
|
79
81
|
}}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</ReactMarkdown>
|
|
82
|
+
*/
|
|
83
|
+
/>
|
|
83
84
|
</article>
|
|
84
85
|
</div>
|
|
85
86
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { HeadlessLink } from '../_utils/headlessParam';
|
|
2
2
|
|
|
3
3
|
export type FooterLink = {
|
|
4
4
|
title: string;
|
|
@@ -33,14 +33,14 @@ export function Footer(props: FooterProps) {
|
|
|
33
33
|
<h3 className="font-bold">Product</h3>
|
|
34
34
|
<ul className="space-y-2 text-sm">
|
|
35
35
|
<li>
|
|
36
|
-
<
|
|
36
|
+
<HeadlessLink href="/get-started" className="text-gray-500 hover:text-gray-900">
|
|
37
37
|
Get started
|
|
38
|
-
</
|
|
38
|
+
</HeadlessLink>
|
|
39
39
|
</li>
|
|
40
40
|
<li>
|
|
41
|
-
<
|
|
41
|
+
<HeadlessLink href="/manifest" className="text-gray-500 hover:text-gray-900">
|
|
42
42
|
Manifest
|
|
43
|
-
</
|
|
43
|
+
</HeadlessLink>
|
|
44
44
|
</li>
|
|
45
45
|
<li>
|
|
46
46
|
<a
|
|
@@ -51,14 +51,14 @@ export function Footer(props: FooterProps) {
|
|
|
51
51
|
</a>
|
|
52
52
|
</li>
|
|
53
53
|
<li>
|
|
54
|
-
<
|
|
54
|
+
<HeadlessLink href="/terms" className="text-gray-500 hover:text-gray-900">
|
|
55
55
|
Terms of Service
|
|
56
|
-
</
|
|
56
|
+
</HeadlessLink>
|
|
57
57
|
</li>
|
|
58
58
|
<li>
|
|
59
|
-
<
|
|
59
|
+
<HeadlessLink href="/privacy" className="text-gray-500 hover:text-gray-900">
|
|
60
60
|
Privacy Policy
|
|
61
|
-
</
|
|
61
|
+
</HeadlessLink>
|
|
62
62
|
</li>
|
|
63
63
|
</ul>
|
|
64
64
|
</div>
|
|
@@ -86,9 +86,9 @@ export function Footer(props: FooterProps) {
|
|
|
86
86
|
</a>
|
|
87
87
|
</li>
|
|
88
88
|
<li>
|
|
89
|
-
<
|
|
89
|
+
<HeadlessLink href="/design" className="text-gray-500 hover:text-gray-900">
|
|
90
90
|
Logos & Branding
|
|
91
|
-
</
|
|
91
|
+
</HeadlessLink>
|
|
92
92
|
</li>
|
|
93
93
|
</ul>
|
|
94
94
|
</div>
|
|
@@ -122,9 +122,9 @@ export function Footer(props: FooterProps) {
|
|
|
122
122
|
</a>
|
|
123
123
|
</li>
|
|
124
124
|
<li>
|
|
125
|
-
<
|
|
125
|
+
<HeadlessLink href="/contact" className="text-gray-500 hover:text-gray-900">
|
|
126
126
|
More
|
|
127
|
-
</
|
|
127
|
+
</HeadlessLink>
|
|
128
128
|
</li>
|
|
129
129
|
</ul>
|
|
130
130
|
</div>
|
|
@@ -4,7 +4,7 @@ import promptbookLogoBlueTransparent from '@/public/logo-blue-white-256.png';
|
|
|
4
4
|
import { $createAgentAction, logoutAction } from '@/src/app/actions';
|
|
5
5
|
import { ArrowRight, ChevronDown, Lock, LogIn, LogOut, User } from 'lucide-react';
|
|
6
6
|
import Image from 'next/image';
|
|
7
|
-
import
|
|
7
|
+
import { HeadlessLink, useIsHeadless, pushWithHeadless } from '../_utils/headlessParam';
|
|
8
8
|
import { useRouter } from 'next/navigation';
|
|
9
9
|
import { ReactNode, useState } from 'react';
|
|
10
10
|
import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
|
|
@@ -41,6 +41,11 @@ type HeaderProps = {
|
|
|
41
41
|
* List of agents
|
|
42
42
|
*/
|
|
43
43
|
agents: Array<AgentBasicInformation>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* List of federated servers for navigation dropdown
|
|
47
|
+
*/
|
|
48
|
+
federatedServers: Array<{ url: string; title: string; logoUrl?: string | null }>;
|
|
44
49
|
};
|
|
45
50
|
|
|
46
51
|
/* TODO: [🐱🚀] Make this Agents server native */
|
|
@@ -70,7 +75,7 @@ type MenuItem =
|
|
|
70
75
|
};
|
|
71
76
|
|
|
72
77
|
export function Header(props: HeaderProps) {
|
|
73
|
-
const { isAdmin = false, currentUser = null, serverName, serverLogoUrl, agents } = props;
|
|
78
|
+
const { isAdmin = false, currentUser = null, serverName, serverLogoUrl, agents, federatedServers } = props;
|
|
74
79
|
|
|
75
80
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
76
81
|
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
@@ -86,6 +91,7 @@ export function Header(props: HeaderProps) {
|
|
|
86
91
|
const [isMobileSystemOpen, setIsMobileSystemOpen] = useState(false);
|
|
87
92
|
const [isCreatingAgent, setIsCreatingAgent] = useState(false);
|
|
88
93
|
const router = useRouter();
|
|
94
|
+
const isHeadless = useIsHeadless();
|
|
89
95
|
|
|
90
96
|
const { users: adminUsers } = useUsersAdmin();
|
|
91
97
|
|
|
@@ -98,7 +104,7 @@ export function Header(props: HeaderProps) {
|
|
|
98
104
|
const agentName = await $createAgentAction();
|
|
99
105
|
|
|
100
106
|
if (agentName) {
|
|
101
|
-
router
|
|
107
|
+
pushWithHeadless(router, `/agents/${agentName}`, isHeadless);
|
|
102
108
|
setIsAgentsOpen(false);
|
|
103
109
|
setIsMenuOpen(false);
|
|
104
110
|
} else {
|
|
@@ -109,6 +115,37 @@ export function Header(props: HeaderProps) {
|
|
|
109
115
|
}
|
|
110
116
|
};
|
|
111
117
|
|
|
118
|
+
// Federated servers dropdown items (respect logo, only current is not clickable)
|
|
119
|
+
const [isFederatedOpen, setIsFederatedOpen] = useState(false);
|
|
120
|
+
const [isMobileFederatedOpen, setIsMobileFederatedOpen] = useState(false);
|
|
121
|
+
|
|
122
|
+
const federatedDropdownItems: SubMenuItem[] = federatedServers.map(server => {
|
|
123
|
+
const isCurrent = server.url === (typeof window !== 'undefined' ? window.location.origin : '');
|
|
124
|
+
return isCurrent
|
|
125
|
+
? {
|
|
126
|
+
label: (
|
|
127
|
+
<span className="flex items-center gap-2">
|
|
128
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
129
|
+
<img src={server.logoUrl || serverLogoUrl || promptbookLogoBlueTransparent.src} alt={server.title} width={20} height={20} className="w-5 h-5 object-contain rounded-full" />
|
|
130
|
+
<span className="font-semibold">{server.title.replace(/^Federated: /, '')}</span>
|
|
131
|
+
<span className="ml-1 text-xs text-blue-600">(current)</span>
|
|
132
|
+
</span>
|
|
133
|
+
),
|
|
134
|
+
isBold: true,
|
|
135
|
+
isBordered: true,
|
|
136
|
+
}
|
|
137
|
+
: {
|
|
138
|
+
label: (
|
|
139
|
+
<span className="flex items-center gap-2">
|
|
140
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
141
|
+
<img src={server.logoUrl || promptbookLogoBlueTransparent.src} alt={server.title} width={20} height={20} className="w-5 h-5 object-contain rounded-full" />
|
|
142
|
+
<span>{server.title.replace(/^Federated: /, '')}</span>
|
|
143
|
+
</span>
|
|
144
|
+
),
|
|
145
|
+
href: server.url,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
112
149
|
// Menu items configuration (DRY principle)
|
|
113
150
|
const menuItems: MenuItem[] = [
|
|
114
151
|
{
|
|
@@ -257,8 +294,8 @@ export function Header(props: HeaderProps) {
|
|
|
257
294
|
<ChangePasswordDialog isOpen={isChangePasswordOpen} onClose={() => setIsChangePasswordOpen(false)} />
|
|
258
295
|
<div className="container mx-auto px-4 h-full">
|
|
259
296
|
<div className="flex items-center justify-between h-full">
|
|
260
|
-
{/* Logo */}
|
|
261
|
-
<
|
|
297
|
+
{/* Logo and heading */}
|
|
298
|
+
<HeadlessLink href="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
|
262
299
|
{serverLogoUrl ? (
|
|
263
300
|
// Note: `next/image` does not load external images well without extra config
|
|
264
301
|
// eslint-disable-next-line @next/next/no-img-element
|
|
@@ -279,20 +316,58 @@ export function Header(props: HeaderProps) {
|
|
|
279
316
|
/>
|
|
280
317
|
)}
|
|
281
318
|
<h1 className="text-xl font-bold tracking-tight text-gray-900">{serverName}</h1>
|
|
282
|
-
</
|
|
319
|
+
</HeadlessLink>
|
|
283
320
|
|
|
284
321
|
{/* Desktop Navigation */}
|
|
285
322
|
<nav className="hidden lg:flex items-center gap-8">
|
|
323
|
+
{/* Federated servers dropdown */}
|
|
324
|
+
<div className="relative">
|
|
325
|
+
<button
|
|
326
|
+
className="flex items-center gap-1 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
327
|
+
onClick={() => setIsFederatedOpen(!isFederatedOpen)}
|
|
328
|
+
onBlur={() => setTimeout(() => setIsFederatedOpen(false), 200)}
|
|
329
|
+
>
|
|
330
|
+
<ChevronDown className="w-4 h-4" />
|
|
331
|
+
<span>Switch server</span>
|
|
332
|
+
</button>
|
|
333
|
+
{isFederatedOpen && (
|
|
334
|
+
<div className="absolute top-full left-0 mt-2 w-56 bg-white rounded-md shadow-lg border border-gray-100 py-1 z-50 animate-in fade-in zoom-in-95 duration-200 max-h-[80vh] overflow-y-auto">
|
|
335
|
+
{federatedDropdownItems.map((subItem, subIndex) => {
|
|
336
|
+
const className = `block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 hover:text-gray-900 ${
|
|
337
|
+
subItem.isBold ? 'font-medium' : ''
|
|
338
|
+
} ${subItem.isBordered ? 'border-b border-gray-100' : ''}`;
|
|
339
|
+
|
|
340
|
+
if (subItem.href) {
|
|
341
|
+
return (
|
|
342
|
+
<HeadlessLink
|
|
343
|
+
key={subIndex}
|
|
344
|
+
href={subItem.href}
|
|
345
|
+
className={className}
|
|
346
|
+
onClick={() => setIsFederatedOpen(false)}
|
|
347
|
+
>
|
|
348
|
+
{subItem.label}
|
|
349
|
+
</HeadlessLink>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return (
|
|
353
|
+
<span key={subIndex} className={className}>
|
|
354
|
+
{subItem.label}
|
|
355
|
+
</span>
|
|
356
|
+
);
|
|
357
|
+
})}
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
286
361
|
{menuItems.map((item, index) => {
|
|
287
362
|
if (item.type === 'link') {
|
|
288
363
|
return (
|
|
289
|
-
<
|
|
364
|
+
<HeadlessLink
|
|
290
365
|
key={index}
|
|
291
366
|
href={item.href}
|
|
292
367
|
className="text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
293
368
|
>
|
|
294
369
|
{item.label}
|
|
295
|
-
</
|
|
370
|
+
</HeadlessLink>
|
|
296
371
|
);
|
|
297
372
|
}
|
|
298
373
|
|
|
@@ -328,14 +403,14 @@ export function Header(props: HeaderProps) {
|
|
|
328
403
|
}
|
|
329
404
|
|
|
330
405
|
return (
|
|
331
|
-
<
|
|
406
|
+
<HeadlessLink
|
|
332
407
|
key={subIndex}
|
|
333
408
|
href={subItem.href!}
|
|
334
409
|
className={className}
|
|
335
410
|
onClick={() => item.setIsOpen(false)}
|
|
336
411
|
>
|
|
337
412
|
{subItem.label}
|
|
338
|
-
</
|
|
413
|
+
</HeadlessLink>
|
|
339
414
|
);
|
|
340
415
|
})}
|
|
341
416
|
</div>
|
|
@@ -348,25 +423,25 @@ export function Header(props: HeaderProps) {
|
|
|
348
423
|
})}
|
|
349
424
|
|
|
350
425
|
{just(false /* TODO: [🧠] Figure out what to do with theese links */) && (
|
|
351
|
-
<
|
|
426
|
+
<a
|
|
352
427
|
href="https://ptbk.io/"
|
|
353
428
|
target="_blank"
|
|
354
429
|
className="text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
355
430
|
>
|
|
356
431
|
Create your server
|
|
357
|
-
</
|
|
432
|
+
</a>
|
|
358
433
|
)}
|
|
359
434
|
</nav>
|
|
360
435
|
|
|
361
436
|
{/* CTA Button & Mobile Menu Toggle */}
|
|
362
437
|
<div className="flex items-center gap-4">
|
|
363
438
|
{just(false /* TODO: [🧠] Figure out what to do with call to action */) && (
|
|
364
|
-
<
|
|
439
|
+
<a href="https://ptbk.io/?modal=get-started" target="_blank" className="hidden md:block">
|
|
365
440
|
<button className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2 bg-promptbook-blue-dark text-white hover:bg-promptbook-blue-dark/90">
|
|
366
441
|
Get Started
|
|
367
442
|
<ArrowRight className="ml-2 w-4 h-4" />
|
|
368
443
|
</button>
|
|
369
|
-
</
|
|
444
|
+
</a>
|
|
370
445
|
)}
|
|
371
446
|
|
|
372
447
|
{!currentUser && !isAdmin && (
|
|
@@ -508,14 +583,14 @@ export function Header(props: HeaderProps) {
|
|
|
508
583
|
{menuItems.map((item, index) => {
|
|
509
584
|
if (item.type === 'link') {
|
|
510
585
|
return (
|
|
511
|
-
<
|
|
586
|
+
<HeadlessLink
|
|
512
587
|
key={index}
|
|
513
588
|
href={item.href}
|
|
514
589
|
className="block text-base font-medium text-gray-600 hover:text-gray-900 py-2"
|
|
515
590
|
onClick={() => setIsMenuOpen(false)}
|
|
516
591
|
>
|
|
517
592
|
{item.label}
|
|
518
|
-
</
|
|
593
|
+
</HeadlessLink>
|
|
519
594
|
);
|
|
520
595
|
}
|
|
521
596
|
|
|
@@ -555,14 +630,14 @@ export function Header(props: HeaderProps) {
|
|
|
555
630
|
}
|
|
556
631
|
|
|
557
632
|
return (
|
|
558
|
-
<
|
|
633
|
+
<HeadlessLink
|
|
559
634
|
key={subIndex}
|
|
560
635
|
href={subItem.href!}
|
|
561
636
|
className={className}
|
|
562
637
|
onClick={() => setIsMenuOpen(false)}
|
|
563
638
|
>
|
|
564
639
|
{subItem.label}
|
|
565
|
-
</
|
|
640
|
+
</HeadlessLink>
|
|
566
641
|
);
|
|
567
642
|
})}
|
|
568
643
|
</div>
|
|
@@ -575,14 +650,14 @@ export function Header(props: HeaderProps) {
|
|
|
575
650
|
})}
|
|
576
651
|
|
|
577
652
|
{just(false /* TODO: [🧠] Figure out what to do with these links */) && (
|
|
578
|
-
<
|
|
653
|
+
<a
|
|
579
654
|
href="https://ptbk.io/"
|
|
580
655
|
target="_blank"
|
|
581
656
|
className="text-base font-medium text-gray-600 hover:text-gray-900 py-2"
|
|
582
657
|
onClick={() => setIsMenuOpen(false)}
|
|
583
658
|
>
|
|
584
659
|
Create your server
|
|
585
|
-
</
|
|
660
|
+
</a>
|
|
586
661
|
)}
|
|
587
662
|
</nav>
|
|
588
663
|
</div>
|
|
@@ -15,6 +15,7 @@ type LayoutWrapperProps = {
|
|
|
15
15
|
agents: Array<AgentBasicInformation>;
|
|
16
16
|
isFooterShown: boolean;
|
|
17
17
|
footerLinks: Array<FooterLink>;
|
|
18
|
+
federatedServers: Array<{ url: string; title: string }>;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export function LayoutWrapper({
|
|
@@ -26,6 +27,7 @@ export function LayoutWrapper({
|
|
|
26
27
|
agents,
|
|
27
28
|
isFooterShown,
|
|
28
29
|
footerLinks,
|
|
30
|
+
federatedServers,
|
|
29
31
|
}: LayoutWrapperProps) {
|
|
30
32
|
const pathname = usePathname();
|
|
31
33
|
const searchParams = useSearchParams();
|
|
@@ -46,6 +48,7 @@ export function LayoutWrapper({
|
|
|
46
48
|
serverName={serverName}
|
|
47
49
|
serverLogoUrl={serverLogoUrl}
|
|
48
50
|
agents={agents}
|
|
51
|
+
federatedServers={federatedServers}
|
|
49
52
|
/>
|
|
50
53
|
<main className={`pt-[60px]`}>{children}</main>
|
|
51
54
|
{isFooterShown && !isFooterHiddenOnPage && <Footer extraLinks={footerLinks} />}
|