@promptbook/cli 0.103.0-48 → 0.103.0-50
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/README.md +1 -1
- package/apps/agents-server/TODO.txt +6 -5
- package/apps/agents-server/config.ts +130 -0
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
- package/apps/agents-server/public/fonts/download-font.js +22 -0
- package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +11 -0
- package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
- package/apps/agents-server/src/app/actions.ts +37 -2
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
- package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +121 -25
- package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
- package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +29 -10
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +4 -5
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +4 -4
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +117 -106
- package/apps/agents-server/src/app/agents/page.tsx +1 -1
- package/apps/agents-server/src/app/api/agents/route.ts +34 -0
- package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
- package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
- package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
- package/apps/agents-server/src/app/api/upload/route.ts +7 -3
- package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
- package/apps/agents-server/src/app/api/users/route.ts +71 -0
- package/apps/agents-server/src/app/globals.css +35 -1
- package/apps/agents-server/src/app/layout.tsx +43 -23
- package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
- package/apps/agents-server/src/app/metadata/page.tsx +13 -0
- package/apps/agents-server/src/app/not-found.tsx +5 -0
- package/apps/agents-server/src/app/page.tsx +117 -46
- package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
- package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
- package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
- package/apps/agents-server/src/components/Header/Header.tsx +146 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
- package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
- package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
- package/apps/agents-server/src/database/$getTableName.ts +18 -0
- package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
- package/apps/agents-server/src/database/getMetadata.ts +31 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +37 -0
- package/apps/agents-server/src/database/schema.sql +81 -33
- package/apps/agents-server/src/database/schema.ts +35 -1
- package/apps/agents-server/src/middleware.ts +200 -0
- package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
- package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
- package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
- package/apps/agents-server/src/tools/$provideServer.ts +39 -0
- package/apps/agents-server/src/utils/auth.ts +33 -0
- package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
- package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
- package/apps/agents-server/src/utils/getFederatedAgents.ts +66 -0
- package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
- package/apps/agents-server/src/utils/session.ts +50 -0
- package/apps/agents-server/tailwind.config.ts +2 -0
- package/esm/index.es.js +147 -31
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +1 -0
- package/esm/typings/src/_packages/components.index.d.ts +2 -0
- package/esm/typings/src/_packages/types.index.d.ts +2 -0
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
- package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +20 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
- package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/index.d.ts +2 -1
- package/esm/typings/src/config.d.ts +1 -0
- package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
- package/esm/typings/src/errors/WrappedError.d.ts +2 -2
- package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
- package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +19 -3
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +13 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +11 -2
- package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
- package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
- package/esm/typings/src/utils/color/Color.d.ts +7 -0
- package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
- package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
- package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
- package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
- package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
- package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
- package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
- package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
- package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
- package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
- package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +147 -31
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/config.ts.todo +0 -38
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
|
-
import logoImage from '@/public/logo-blue-white-256.png';
|
|
4
3
|
import { getSingleLlmExecutionTools } from '@promptbook-local/core';
|
|
5
4
|
import moment from 'moment';
|
|
6
5
|
import { headers } from 'next/headers';
|
|
7
|
-
import Image from 'next/image';
|
|
8
6
|
import Link from 'next/link';
|
|
9
7
|
import { AvatarProfile } from '../../../../src/book-components/AvatarProfile/AvatarProfile/AvatarProfile';
|
|
10
8
|
import { AboutPromptbookInformation } from '../../../../src/utils/misc/xAboutPromptbookInformation';
|
|
11
9
|
import { $sideEffect } from '../../../../src/utils/organization/$sideEffect';
|
|
10
|
+
import { AuthControls } from '../components/Auth/AuthControls';
|
|
11
|
+
import { UsersList } from '../components/UsersList/UsersList';
|
|
12
|
+
import VercelDeploymentCard from '../components/VercelDeploymentCard/VercelDeploymentCard';
|
|
13
|
+
import { getMetadata } from '../database/getMetadata';
|
|
12
14
|
import { getLongRunningTask } from '../deamons/longRunningTask';
|
|
13
15
|
import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
|
|
14
16
|
import { $provideExecutionToolsForServer } from '../tools/$provideExecutionToolsForServer';
|
|
17
|
+
import { $provideServer } from '../tools/$provideServer';
|
|
18
|
+
import { getFederatedAgents } from '../utils/getFederatedAgents';
|
|
19
|
+
import { getCurrentUser } from '../utils/getCurrentUser';
|
|
20
|
+
import { isUserAdmin } from '../utils/isUserAdmin';
|
|
15
21
|
import { AddAgentButton } from './AddAgentButton';
|
|
16
22
|
|
|
17
23
|
// Add calendar formats that include seconds
|
|
@@ -27,27 +33,39 @@ const calendarWithSeconds = {
|
|
|
27
33
|
export default async function HomePage() {
|
|
28
34
|
$sideEffect(/* Note: [🐶] This will ensure dynamic rendering of page and avoid Next.js pre-render */ headers());
|
|
29
35
|
|
|
36
|
+
const isAdmin = await isUserAdmin(); /* <- TODO: [👹] Here should be user permissions */
|
|
37
|
+
const currentUser = await getCurrentUser();
|
|
38
|
+
|
|
30
39
|
const collection = await $provideAgentCollectionForServer();
|
|
31
40
|
const agents = await collection.listAgents();
|
|
32
41
|
|
|
42
|
+
const federatedServersString = (await getMetadata('FEDERATED_SERVERS')) || '';
|
|
43
|
+
const federatedServers = federatedServersString
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter((s) => s !== '');
|
|
47
|
+
|
|
48
|
+
const externalAgents = await getFederatedAgents(federatedServers);
|
|
49
|
+
|
|
33
50
|
const longRunningTask = getLongRunningTask();
|
|
34
51
|
|
|
35
52
|
const executionTools = await $provideExecutionToolsForServer();
|
|
36
53
|
const models = await getSingleLlmExecutionTools(executionTools.llm).listModels();
|
|
37
54
|
|
|
55
|
+
const host = (await headers()).get('host');
|
|
56
|
+
|
|
38
57
|
return (
|
|
39
58
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
40
59
|
<div className="container mx-auto px-4 py-16">
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
</h1>
|
|
60
|
+
<div className="flex justify-end mb-4">
|
|
61
|
+
<AuthControls initialUser={currentUser} />
|
|
62
|
+
</div>
|
|
45
63
|
|
|
46
64
|
<>
|
|
47
|
-
<h2 className="text-3xl text-gray-900 mt-
|
|
65
|
+
<h2 className="text-3xl text-gray-900 mt-4 mb-4">Agents ({agents.length})</h2>
|
|
48
66
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
49
67
|
{agents.map((agent) => (
|
|
50
|
-
<Link key={agent.agentName} href={
|
|
68
|
+
<Link key={agent.agentName} href={`/${agent.agentName}`}>
|
|
51
69
|
<AvatarProfile
|
|
52
70
|
{...{ agent }}
|
|
53
71
|
style={
|
|
@@ -61,50 +79,103 @@ export default async function HomePage() {
|
|
|
61
79
|
/>
|
|
62
80
|
</Link>
|
|
63
81
|
))}
|
|
64
|
-
<AddAgentButton />
|
|
82
|
+
{isAdmin && <AddAgentButton />}
|
|
65
83
|
</div>
|
|
66
84
|
</>
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
{externalAgents.length > 0 && (
|
|
87
|
+
<>
|
|
88
|
+
<h2 className="text-3xl text-gray-900 mt-16 mb-4">External Agents ({externalAgents.length})</h2>
|
|
89
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
90
|
+
{externalAgents.map((agent) => (
|
|
91
|
+
<Link key={agent.url} href={agent.url}>
|
|
92
|
+
<AvatarProfile
|
|
93
|
+
{...{ agent }}
|
|
94
|
+
style={
|
|
95
|
+
!agent.meta.color
|
|
96
|
+
? {}
|
|
97
|
+
: {
|
|
98
|
+
backgroundColor: `${agent.meta.color}22`, // <- TODO: Use Color object here
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
102
|
+
/>
|
|
103
|
+
</Link>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{isAdmin && <UsersList />}
|
|
110
|
+
|
|
111
|
+
{isAdmin && (
|
|
112
|
+
<>
|
|
113
|
+
<h2 className="text-3xl text-gray-900 mt-16 mb-4">Models ({models.length})</h2>
|
|
114
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
115
|
+
{models.map(({ modelName, modelTitle, modelDescription }) => (
|
|
116
|
+
<Link key={modelName} href={`#[🐱🚀]`}>
|
|
117
|
+
<div className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400">
|
|
118
|
+
<h2 className="text-2xl font-semibold text-gray-900 mb-2">{modelTitle}</h2>
|
|
119
|
+
<code>{modelName}</code>
|
|
120
|
+
<p className="text-gray-600">{modelDescription}</p>
|
|
121
|
+
</div>
|
|
122
|
+
</Link>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{isAdmin && (
|
|
129
|
+
<>
|
|
130
|
+
{/* Note: Shown in <AboutPromptbookInformation />: <h2 className="text-3xl text-gray-900 mt-16 mb-4">About Promptbook</h2> */}
|
|
131
|
+
<AboutPromptbookInformation />
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{isAdmin && (
|
|
136
|
+
<>
|
|
137
|
+
<h2 className="text-3xl text-gray-900 mt-16 mb-4">Technical Information</h2>
|
|
138
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
139
|
+
<Link
|
|
140
|
+
href={'#'}
|
|
141
|
+
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
142
|
+
>
|
|
143
|
+
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
|
144
|
+
Long running task {longRunningTask.taskId}
|
|
145
|
+
</h2>
|
|
146
|
+
<p className="text-gray-600">Tick: {longRunningTask.tick}</p>
|
|
147
|
+
<p className="text-gray-600">
|
|
148
|
+
Created At:{' '}
|
|
149
|
+
{moment(longRunningTask.createdAt).calendar(undefined, calendarWithSeconds)}
|
|
150
|
+
</p>
|
|
151
|
+
<p className="text-gray-600">
|
|
152
|
+
Updated At:{' '}
|
|
153
|
+
{moment(longRunningTask.updatedAt).calendar(undefined, calendarWithSeconds)}
|
|
154
|
+
</p>
|
|
78
155
|
</Link>
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
</>
|
|
82
156
|
|
|
83
|
-
|
|
84
|
-
<h2 className="text-3xl text-gray-900 mt-16 mb-4">About</h2>
|
|
85
|
-
<AboutPromptbookInformation />
|
|
86
|
-
</>
|
|
157
|
+
<VercelDeploymentCard />
|
|
87
158
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
|
|
159
|
+
<Link
|
|
160
|
+
href={'#'}
|
|
161
|
+
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
162
|
+
>
|
|
163
|
+
<h2 className="text-2xl font-semibold text-gray-900 mb-2">HTTP Information</h2>
|
|
164
|
+
|
|
165
|
+
<p className="text-gray-600">Host: {host}</p>
|
|
166
|
+
</Link>
|
|
167
|
+
|
|
168
|
+
<Link
|
|
169
|
+
href={'#'}
|
|
170
|
+
className="block p-6 bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 border border-gray-200 hover:border-blue-400"
|
|
171
|
+
>
|
|
172
|
+
<h2 className="text-2xl font-semibold text-gray-900 mb-2">Server</h2>
|
|
173
|
+
|
|
174
|
+
<pre>{JSON.stringify(await $provideServer(), null, 2)}</pre>
|
|
175
|
+
</Link>
|
|
176
|
+
</div>
|
|
177
|
+
</>
|
|
178
|
+
)}
|
|
108
179
|
</div>
|
|
109
180
|
</div>
|
|
110
181
|
);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
type AuthControlsProps = {
|
|
7
|
+
initialUser: { username: string; isAdmin: boolean } | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function AuthControls({ initialUser }: AuthControlsProps) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const [user, setUser] = useState(initialUser);
|
|
13
|
+
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
14
|
+
const [username, setUsername] = useState('');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const handleLogin = async (e: React.FormEvent) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch('/api/auth/login', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({ username, password }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
throw new Error(data.error || 'Login failed');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Reload page to reflect state
|
|
35
|
+
window.location.reload();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleLogout = async () => {
|
|
42
|
+
try {
|
|
43
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
44
|
+
window.location.reload();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Logout failed', err);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (user) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex items-center space-x-4">
|
|
53
|
+
<span className="text-gray-600">
|
|
54
|
+
Logged in as <strong>{user.username}</strong>
|
|
55
|
+
{user.isAdmin && <span className="ml-2 bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">Admin</span>}
|
|
56
|
+
</span>
|
|
57
|
+
<button
|
|
58
|
+
onClick={handleLogout}
|
|
59
|
+
className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
Logout
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
{!isLoginOpen ? (
|
|
70
|
+
<button
|
|
71
|
+
onClick={() => setIsLoginOpen(true)}
|
|
72
|
+
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors"
|
|
73
|
+
>
|
|
74
|
+
Login
|
|
75
|
+
</button>
|
|
76
|
+
) : (
|
|
77
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
78
|
+
<div className="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
|
|
79
|
+
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
|
80
|
+
{error && <div className="bg-red-100 text-red-700 p-3 rounded mb-4">{error}</div>}
|
|
81
|
+
<form onSubmit={handleLogin} className="space-y-4">
|
|
82
|
+
<div>
|
|
83
|
+
<label className="block text-gray-700 mb-1">Username</label>
|
|
84
|
+
<input
|
|
85
|
+
type="text"
|
|
86
|
+
value={username}
|
|
87
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
88
|
+
className="w-full p-2 border border-gray-300 rounded"
|
|
89
|
+
required
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<label className="block text-gray-700 mb-1">Password</label>
|
|
94
|
+
<input
|
|
95
|
+
type="password"
|
|
96
|
+
value={password}
|
|
97
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
+
className="w-full p-2 border border-gray-300 rounded"
|
|
99
|
+
required
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<div className="flex justify-end space-x-2">
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
onClick={() => setIsLoginOpen(false)}
|
|
106
|
+
className="px-4 py-2 text-gray-600 hover:text-gray-800"
|
|
107
|
+
>
|
|
108
|
+
Cancel
|
|
109
|
+
</button>
|
|
110
|
+
<button
|
|
111
|
+
type="submit"
|
|
112
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
113
|
+
>
|
|
114
|
+
Login
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</form>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type ErrorPageProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The title of the error page (e.g. "404 Not Found")
|
|
6
|
+
*/
|
|
7
|
+
title: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The message to display to the user
|
|
11
|
+
*/
|
|
12
|
+
message: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional children to display below the message (e.g. a button or form)
|
|
16
|
+
*/
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A standard layout for error pages (404, 403, 500, etc.)
|
|
22
|
+
*/
|
|
23
|
+
export function ErrorPage({ title, message, children }: ErrorPageProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
26
|
+
<div className="bg-white p-8 rounded-lg shadow-md max-w-md w-full">
|
|
27
|
+
<h1 className="text-3xl font-bold text-red-600 mb-4 text-center">{title}</h1>
|
|
28
|
+
<p className="text-gray-700 mb-6 text-center">{message}</p>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { ErrorPage } from '../ErrorPage/ErrorPage';
|
|
5
|
+
import { LoginForm } from '../LoginForm/LoginForm';
|
|
6
|
+
|
|
7
|
+
export function ForbiddenPage() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<ErrorPage title="403 Forbidden" message="You do not have permission to access this page.">
|
|
12
|
+
<LoginForm onSuccess={() => router.refresh()} />
|
|
13
|
+
</ErrorPage>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import promptbookLogoBlueTransparent from '@/public/logo-blue-white-256.png';
|
|
4
|
+
import { logoutAction } from '@/src/app/actions';
|
|
5
|
+
import { ArrowRight, LogIn, LogOut, Menu, X } from 'lucide-react';
|
|
6
|
+
import Image from 'next/image';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { just } from '../../../../../src/utils/organization/just';
|
|
10
|
+
import { LoginDialog } from '../LoginDialog/LoginDialog';
|
|
11
|
+
|
|
12
|
+
type HeaderProps = {
|
|
13
|
+
/**
|
|
14
|
+
* Is the user an admin
|
|
15
|
+
*/
|
|
16
|
+
isAdmin?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The name of the server
|
|
20
|
+
*/
|
|
21
|
+
serverName: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The URL of the logo displayed in the heading bar
|
|
25
|
+
*/
|
|
26
|
+
serverLogoUrl: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/* TODO: [🐱🚀] Make this Agents server native */
|
|
30
|
+
|
|
31
|
+
export function Header(props: HeaderProps) {
|
|
32
|
+
const { isAdmin = false, serverName, serverLogoUrl } = props;
|
|
33
|
+
|
|
34
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
35
|
+
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
36
|
+
|
|
37
|
+
const handleLogout = async () => {
|
|
38
|
+
await logoutAction();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200 h-[60px]">
|
|
43
|
+
<LoginDialog isOpen={isLoginOpen} onClose={() => setIsLoginOpen(false)} />
|
|
44
|
+
<div className="container mx-auto px-4">
|
|
45
|
+
<div className="flex items-center justify-between h-16">
|
|
46
|
+
{/* Logo <- TODO: This should be <h1>*/}
|
|
47
|
+
<Link href="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
|
48
|
+
<Image
|
|
49
|
+
src={serverLogoUrl || promptbookLogoBlueTransparent}
|
|
50
|
+
alt={serverName}
|
|
51
|
+
width={32}
|
|
52
|
+
height={32}
|
|
53
|
+
className="w-8 h-8 object-contain"
|
|
54
|
+
/>
|
|
55
|
+
<span className="text-xl text-gray-900">{serverName}</span>
|
|
56
|
+
</Link>
|
|
57
|
+
|
|
58
|
+
{/* Desktop Navigation */}
|
|
59
|
+
{/* Desktop Navigation */}
|
|
60
|
+
<nav className="hidden md:flex items-center gap-8">
|
|
61
|
+
{isAdmin && (
|
|
62
|
+
<Link
|
|
63
|
+
href="/metadata"
|
|
64
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
65
|
+
>
|
|
66
|
+
Metadata
|
|
67
|
+
</Link>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{just(false /* TODO: [🧠] Figure out what to do with theese links */) && (
|
|
71
|
+
<Link
|
|
72
|
+
href="https://ptbk.io/#try-it-yourself"
|
|
73
|
+
target="_blank"
|
|
74
|
+
className="text-gray-600 hover:text-gray-900 transition-colors cursor-pointer"
|
|
75
|
+
>
|
|
76
|
+
Try it yourself
|
|
77
|
+
</Link>
|
|
78
|
+
)}
|
|
79
|
+
</nav>
|
|
80
|
+
|
|
81
|
+
{/* CTA Button & Mobile Menu Toggle */}
|
|
82
|
+
<div className="flex items-center gap-4">
|
|
83
|
+
{just(false /* TODO: [🧠] Figure out what to do with call to action */) && (
|
|
84
|
+
<Link href="https://ptbk.io/?modal=get-started" target="_blank" className="hidden md:block">
|
|
85
|
+
<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">
|
|
86
|
+
Get Started
|
|
87
|
+
<ArrowRight className="ml-2 w-4 h-4" />
|
|
88
|
+
</button>
|
|
89
|
+
</Link>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{!isAdmin ? (
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => {
|
|
95
|
+
setIsLoginOpen(true);
|
|
96
|
+
setIsMenuOpen(false);
|
|
97
|
+
}}
|
|
98
|
+
className="w-full inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-10 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100"
|
|
99
|
+
>
|
|
100
|
+
Log in
|
|
101
|
+
<LogIn className="ml-2 w-4 h-4" />
|
|
102
|
+
</button>
|
|
103
|
+
) : (
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => {
|
|
106
|
+
handleLogout();
|
|
107
|
+
setIsMenuOpen(false);
|
|
108
|
+
}}
|
|
109
|
+
className="w-full inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium h-10 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100"
|
|
110
|
+
>
|
|
111
|
+
Log out
|
|
112
|
+
<LogOut className="ml-2 w-4 h-4" />
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Mobile Menu Toggle */}
|
|
117
|
+
{just(false /* TODO: [🧠] Figure out whether we want a menu */) && (
|
|
118
|
+
<button
|
|
119
|
+
className="md:hidden p-2 text-gray-600 hover:text-gray-900"
|
|
120
|
+
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
121
|
+
>
|
|
122
|
+
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Mobile Navigation */}
|
|
129
|
+
{just(false /* TODO: [🧠] Figure out whether we want a menu */) && isMenuOpen && (
|
|
130
|
+
<div className="md:hidden py-4 border-t border-gray-100 animate-in slide-in-from-top-2">
|
|
131
|
+
<nav className="flex flex-col gap-4">
|
|
132
|
+
<Link
|
|
133
|
+
href="https://ptbk.io/#try-it-yourself"
|
|
134
|
+
target="_blank"
|
|
135
|
+
className="text-gray-600 hover:text-gray-900 transition-colors py-2"
|
|
136
|
+
onClick={() => setIsMenuOpen(false)}
|
|
137
|
+
>
|
|
138
|
+
Try it yourself
|
|
139
|
+
</Link>
|
|
140
|
+
</nav>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</header>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation';
|
|
4
|
+
import { Header } from '../Header/Header';
|
|
5
|
+
|
|
6
|
+
type LayoutWrapperProps = {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
isAdmin: boolean;
|
|
9
|
+
serverName: string;
|
|
10
|
+
serverLogoUrl: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function LayoutWrapper({ children, isAdmin, serverName, serverLogoUrl }: LayoutWrapperProps) {
|
|
14
|
+
const pathname = usePathname();
|
|
15
|
+
const isHeaderHidden = pathname?.includes('/chat');
|
|
16
|
+
|
|
17
|
+
if (isHeaderHidden) {
|
|
18
|
+
return <main className={`pt-0`}>{children}</main>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Header isAdmin={isAdmin} serverName={serverName} serverLogoUrl={serverLogoUrl} />
|
|
24
|
+
<main className={`pt-[60px]`}>{children}</main>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { LoginForm } from '../LoginForm/LoginForm';
|
|
5
|
+
|
|
6
|
+
type LoginDialogProps = {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function LoginDialog(props: LoginDialogProps) {
|
|
12
|
+
const { isOpen, onClose } = props;
|
|
13
|
+
|
|
14
|
+
if (!isOpen) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in duration-200">
|
|
20
|
+
<div className="relative w-full max-w-md bg-white rounded-lg shadow-lg border border-gray-200 p-6 animate-in zoom-in-95 duration-200">
|
|
21
|
+
<button
|
|
22
|
+
onClick={onClose}
|
|
23
|
+
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 transition-colors"
|
|
24
|
+
>
|
|
25
|
+
<X className="w-5 h-5" />
|
|
26
|
+
<span className="sr-only">Close</span>
|
|
27
|
+
</button>
|
|
28
|
+
|
|
29
|
+
<div className="mb-6">
|
|
30
|
+
<h2 className="text-xl font-semibold text-gray-900">Log in</h2>
|
|
31
|
+
<p className="text-sm text-gray-500 mt-1">
|
|
32
|
+
Enter your credentials to access the admin area
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<LoginForm onSuccess={onClose} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|