@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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
2
|
+
import { parseAgentSource } from '@promptbook-local/core';
|
|
3
|
+
import { Metadata } from 'next';
|
|
4
|
+
|
|
5
|
+
export async function generateAgentMetadata({ params }: { params: Promise<{ agentName: string }> }): Promise<Metadata> {
|
|
6
|
+
let { agentName } = await params;
|
|
7
|
+
agentName = decodeURIComponent(agentName);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const collection = await $provideAgentCollectionForServer();
|
|
11
|
+
const agentSource = await collection.getAgentSource(agentName);
|
|
12
|
+
const agentProfile = parseAgentSource(agentSource);
|
|
13
|
+
|
|
14
|
+
const title = agentProfile.meta.title || agentProfile.agentName;
|
|
15
|
+
const description = agentProfile.meta.description || agentProfile.personaDescription || undefined;
|
|
16
|
+
|
|
17
|
+
// Extract image from meta
|
|
18
|
+
const image = agentProfile.meta.image;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
openGraph: {
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
type: 'website',
|
|
27
|
+
images: image ? [{ url: image }] : undefined,
|
|
28
|
+
},
|
|
29
|
+
twitter: {
|
|
30
|
+
card: 'summary_large_image',
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
images: image ? [image] : undefined,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn(`Failed to generate metadata for agent ${agentName}`, error);
|
|
38
|
+
return {
|
|
39
|
+
title: agentName,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
|
|
4
|
-
import { PromptbookQrCode } from '@promptbook-local/components';
|
|
5
4
|
// import { BookEditor } from '@promptbook-local/components';
|
|
5
|
+
import { $provideServer } from '@/src/tools/$provideServer';
|
|
6
6
|
import { parseAgentSource } from '@promptbook-local/core';
|
|
7
|
+
import { Columns2Icon, MessagesSquareIcon, NotebookPenIcon } from 'lucide-react';
|
|
7
8
|
import { headers } from 'next/headers';
|
|
9
|
+
import { notFound } from 'next/navigation';
|
|
10
|
+
import { Color } from '../../../../../../src/utils/color/Color';
|
|
11
|
+
import { withAlpha } from '../../../../../../src/utils/color/operators/withAlpha';
|
|
8
12
|
import { $sideEffect } from '../../../../../../src/utils/organization/$sideEffect';
|
|
9
|
-
import {
|
|
13
|
+
import { AgentChatWrapper } from './AgentChatWrapper';
|
|
14
|
+
import { AgentQrCode } from './AgentQrCode';
|
|
15
|
+
import { CopyField } from './CopyField';
|
|
16
|
+
import { generateAgentMetadata } from './generateAgentMetadata';
|
|
10
17
|
// import { Agent } from '@promptbook-local/core';
|
|
11
18
|
// import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
|
|
12
19
|
// import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
|
|
13
20
|
|
|
21
|
+
export const generateMetadata = generateAgentMetadata;
|
|
22
|
+
|
|
14
23
|
export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
|
|
15
24
|
// const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
|
|
16
25
|
// const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
|
|
@@ -22,141 +31,143 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
|
|
|
22
31
|
agentName = decodeURIComponent(agentName);
|
|
23
32
|
|
|
24
33
|
const collection = await $provideAgentCollectionForServer();
|
|
25
|
-
|
|
34
|
+
let agentSource;
|
|
35
|
+
try {
|
|
36
|
+
agentSource = await collection.getAgentSource(agentName);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (
|
|
39
|
+
error instanceof Error &&
|
|
40
|
+
// Note: This is a bit hacky, but valid way to check for specific error message
|
|
41
|
+
(error.message.includes('Cannot coerce the result to a single JSON object') ||
|
|
42
|
+
error.message.includes('JSON object requested, multiple (or no) results returned'))
|
|
43
|
+
) {
|
|
44
|
+
notFound();
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
26
48
|
const agentProfile = parseAgentSource(agentSource);
|
|
27
49
|
|
|
50
|
+
const { publicUrl } = await $provideServer();
|
|
51
|
+
|
|
28
52
|
// Build agent page URL for QR and copy
|
|
29
|
-
const
|
|
30
|
-
// <- TODO:
|
|
53
|
+
const agentUrl = `${publicUrl.href}${encodeURIComponent(agentName)}`;
|
|
54
|
+
// <- TODO: [🐱🚀] Better
|
|
55
|
+
|
|
56
|
+
const agentEmail = `${agentName}@${publicUrl.hostname}`;
|
|
57
|
+
|
|
58
|
+
console.log('[🐱🚀]', { pageUrl: agentUrl });
|
|
31
59
|
|
|
32
60
|
// Extract brand color from meta
|
|
33
|
-
const brandColor = agentProfile.meta.color || '#3b82f6'; // Default to blue-600
|
|
61
|
+
const brandColor = Color.from(agentProfile.meta.color || '#3b82f6'); // Default to blue-600
|
|
34
62
|
|
|
35
63
|
// Mock agent actions
|
|
36
|
-
const agentActions = ['Emails', 'Web', '
|
|
37
|
-
|
|
38
|
-
// Render agent profile fields
|
|
39
|
-
const renderProfileFields = () => {
|
|
40
|
-
const renderValue = (value: unknown): React.ReactNode => {
|
|
41
|
-
if (value === null || value === undefined) {
|
|
42
|
-
return <span className="text-gray-400 italic">Not specified</span>;
|
|
43
|
-
}
|
|
44
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
45
|
-
const objValue = value as Record<string, unknown>;
|
|
46
|
-
return (
|
|
47
|
-
<div className="space-y-1 pl-3 border-l-2 border-gray-200">
|
|
48
|
-
{Object.entries(objValue).map(([subKey, subValue]) => (
|
|
49
|
-
<div key={subKey} className="flex gap-2">
|
|
50
|
-
<span className="text-xs text-gray-600 font-medium">{subKey}:</span>
|
|
51
|
-
<span className="text-sm text-gray-700">{String(subValue)}</span>
|
|
52
|
-
</div>
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
if (Array.isArray(value)) {
|
|
58
|
-
return (
|
|
59
|
-
<ul className="list-disc list-inside space-y-0.5">
|
|
60
|
-
{value.map((item, idx) => (
|
|
61
|
-
<li key={idx} className="text-sm text-gray-700">
|
|
62
|
-
{String(item)}
|
|
63
|
-
</li>
|
|
64
|
-
))}
|
|
65
|
-
</ul>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return <span className="text-base text-gray-800 break-words">{String(value)}</span>;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="space-y-4">
|
|
73
|
-
{Object.entries(agentProfile).map(([key, value]) => (
|
|
74
|
-
<div key={key} className="flex flex-col gap-1">
|
|
75
|
-
<span className="text-xs text-gray-500 font-semibold uppercase tracking-wide">{key}</span>
|
|
76
|
-
{renderValue(value)}
|
|
77
|
-
</div>
|
|
78
|
-
))}
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
};
|
|
64
|
+
const agentActions = ['Emails', 'Web chat', 'Read documents', 'Browser', 'WhatsApp', '<Coding/>'];
|
|
82
65
|
|
|
83
66
|
return (
|
|
84
|
-
<div
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
67
|
+
<div className="flex flex-col md:flex-row h-[calc(100vh-60px)] w-full overflow-hidden">
|
|
68
|
+
{/* Left sidebar: Profile info */}
|
|
69
|
+
<div
|
|
70
|
+
className="w-full md:w-[400px] flex flex-col gap-6 p-6 overflow-y-auto border-r bg-gray-50 flex-shrink-0"
|
|
71
|
+
style={{
|
|
72
|
+
backgroundColor: brandColor.then(withAlpha(0.05)).toHex(),
|
|
73
|
+
borderColor: brandColor.then(withAlpha(0.1)).toHex(),
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<div className="flex items-center gap-4">
|
|
77
|
+
{agentProfile.meta.image && (
|
|
78
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
79
|
+
<img
|
|
80
|
+
src={agentProfile.meta.image as string}
|
|
81
|
+
alt={agentProfile.agentName || 'Agent'}
|
|
82
|
+
width={64}
|
|
83
|
+
height={64}
|
|
84
|
+
className="rounded-full object-cover border-2 aspect-square w-16 h-16"
|
|
85
|
+
style={{ borderColor: brandColor.toHex() }}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
<div className="flex-1">
|
|
89
|
+
<h1 className="text-3xl font-bold text-gray-900 break-words">{agentProfile.agentName}</h1>
|
|
90
|
+
<span
|
|
91
|
+
className="inline-block mt-1 px-2 py-1 rounded text-xs font-semibold text-white"
|
|
92
|
+
style={{ backgroundColor: brandColor.toHex() }}
|
|
93
|
+
>
|
|
94
|
+
Agent
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<p className="text-gray-700">{agentProfile.personaDescription}</p>
|
|
100
|
+
|
|
101
|
+
<div className="flex flex-col gap-2">
|
|
102
|
+
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Capabilities</h2>
|
|
103
|
+
<div className="flex flex-wrap gap-2">
|
|
104
|
+
{agentActions.map((action) => (
|
|
105
105
|
<span
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
key={action}
|
|
107
|
+
className="px-3 py-1 bg-white text-gray-700 rounded-full text-xs font-medium border border-gray-200 shadow-sm"
|
|
108
108
|
>
|
|
109
|
-
|
|
109
|
+
{action}
|
|
110
110
|
</span>
|
|
111
|
-
|
|
112
|
-
</div>
|
|
113
|
-
{renderProfileFields()}
|
|
114
|
-
<div className="flex flex-col gap-2">
|
|
115
|
-
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Actions</h2>
|
|
116
|
-
<div className="flex flex-wrap gap-2">
|
|
117
|
-
{agentActions.map((action) => (
|
|
118
|
-
<span
|
|
119
|
-
key={action}
|
|
120
|
-
className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-xs font-medium border border-gray-200"
|
|
121
|
-
>
|
|
122
|
-
{action}
|
|
123
|
-
</span>
|
|
124
|
-
))}
|
|
125
|
-
</div>
|
|
111
|
+
))}
|
|
126
112
|
</div>
|
|
127
|
-
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div className="flex flex-col gap-2 mt-auto">
|
|
116
|
+
<div className="flex gap-2">
|
|
128
117
|
<a
|
|
129
|
-
href={
|
|
118
|
+
href={`/agents/${encodeURIComponent(agentName)}/chat`}
|
|
130
119
|
// <- TODO: [🧠] Can I append path like this on current browser URL in href?
|
|
131
|
-
className="
|
|
120
|
+
className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
|
|
132
121
|
>
|
|
133
|
-
|
|
122
|
+
<MessagesSquareIcon className="ml-2 w-4 h-4 mr-2" />
|
|
123
|
+
Chat
|
|
134
124
|
</a>
|
|
135
125
|
<a
|
|
136
|
-
href={
|
|
126
|
+
href={`/agents/${encodeURIComponent(agentName)}/book+chat`}
|
|
137
127
|
// <- TODO: [🧠] Can I append path like this on current browser URL in href?
|
|
138
|
-
className="
|
|
128
|
+
className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
|
|
139
129
|
>
|
|
140
|
-
|
|
130
|
+
<Columns2Icon className="ml-2 w-4 h-4 mr-2" />
|
|
131
|
+
Book + Chat
|
|
132
|
+
</a>
|
|
133
|
+
<a
|
|
134
|
+
href={`/agents/${encodeURIComponent(agentName)}/book`}
|
|
135
|
+
// <- TODO: [🧠] Can I append path like this on current browser URL in href?
|
|
136
|
+
className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
|
|
137
|
+
>
|
|
138
|
+
<NotebookPenIcon className="ml-2 w-4 h-4 mr-2" />
|
|
139
|
+
Edit
|
|
141
140
|
</a>
|
|
142
141
|
</div>
|
|
143
142
|
</div>
|
|
144
|
-
|
|
145
|
-
<div className="flex flex-col items-center gap-6
|
|
146
|
-
<div className="bg-
|
|
147
|
-
<
|
|
148
|
-
|
|
143
|
+
|
|
144
|
+
<div className="flex flex-col items-center gap-4 pt-6 border-t border-gray-200 w-full">
|
|
145
|
+
<div className="bg-white rounded-lg p-4 flex flex-col items-center shadow-sm border border-gray-100">
|
|
146
|
+
<AgentQrCode
|
|
147
|
+
agentName={agentProfile.agentName || 'Agent'}
|
|
148
|
+
personaDescription={agentProfile.personaDescription}
|
|
149
|
+
agentUrl={agentUrl}
|
|
150
|
+
agentEmail={agentEmail}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
<div className="flex flex-col gap-2 w-full">
|
|
154
|
+
<CopyField label="Agent Page URL" value={agentUrl} />
|
|
155
|
+
<CopyField label="Agent Email" value={agentEmail} />
|
|
149
156
|
</div>
|
|
150
|
-
<AgentUrlCopy url={pageUrl} />
|
|
151
157
|
</div>
|
|
152
158
|
</div>
|
|
159
|
+
|
|
160
|
+
{/* Main content: Chat */}
|
|
161
|
+
<div className="flex-1 relative h-full bg-white">
|
|
162
|
+
<AgentChatWrapper agentUrl={agentUrl} />
|
|
163
|
+
</div>
|
|
153
164
|
</div>
|
|
154
165
|
);
|
|
155
166
|
}
|
|
156
167
|
|
|
157
168
|
/**
|
|
158
|
-
* TODO:
|
|
159
|
-
* TODO:
|
|
169
|
+
* TODO: [🐱🚀] Make this page look nice - 🃏
|
|
170
|
+
* TODO: [🐱🚀] Show usage of LLM
|
|
160
171
|
* TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
|
|
161
172
|
* TODO: [🎣][🧠] Maybe do API / Page for transpilers, Allow to export each agent
|
|
162
173
|
*/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getMetadata } from '../../../database/getMetadata';
|
|
3
|
+
import { $provideAgentCollectionForServer } from '../../../tools/$provideAgentCollectionForServer';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
try {
|
|
9
|
+
const collection = await $provideAgentCollectionForServer();
|
|
10
|
+
const agents = await collection.listAgents();
|
|
11
|
+
const serverUrl = (await getMetadata('SERVER_URL')) || '';
|
|
12
|
+
const federatedServersString = (await getMetadata('FEDERATED_SERVERS')) || '';
|
|
13
|
+
const federatedServers = federatedServersString
|
|
14
|
+
.split(',')
|
|
15
|
+
.map((s) => s.trim())
|
|
16
|
+
.filter((s) => s !== '');
|
|
17
|
+
|
|
18
|
+
const agentsWithUrl = agents.map((agent) => ({
|
|
19
|
+
...agent,
|
|
20
|
+
url: `${serverUrl}/${agent.agentName}`,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
return NextResponse.json({
|
|
24
|
+
agents: agentsWithUrl,
|
|
25
|
+
federatedServers,
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error fetching agents:', error);
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'Failed to fetch agents' },
|
|
31
|
+
{ status: 500 },
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { $getTableName } from '@/src/database/$getTableName';
|
|
2
|
+
import { $provideSupabaseForServer } from '../../../../database/$provideSupabaseForServer';
|
|
3
|
+
import { AgentsServerDatabase } from '../../../../database/schema';
|
|
4
|
+
import { verifyPassword } from '../../../../utils/auth';
|
|
5
|
+
import { setSession } from '../../../../utils/session';
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
|
|
8
|
+
export async function POST(request: Request) {
|
|
9
|
+
try {
|
|
10
|
+
const body = await request.json();
|
|
11
|
+
const { username, password } = body;
|
|
12
|
+
|
|
13
|
+
if (!username || !password) {
|
|
14
|
+
return NextResponse.json({ error: 'Username and password are required' }, { status: 400 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 1. Check if it's the environment admin
|
|
18
|
+
if (process.env.ADMIN_PASSWORD && password === process.env.ADMIN_PASSWORD && username === 'admin') {
|
|
19
|
+
// Or maybe allow any username if password matches admin password?
|
|
20
|
+
// The task says "process.env.ADMIN_PASSWORD is like one of the admin users"
|
|
21
|
+
// Assuming username 'admin' for environment password login.
|
|
22
|
+
await setSession({ username: 'admin', isAdmin: true });
|
|
23
|
+
return NextResponse.json({ success: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Check DB users
|
|
27
|
+
const supabase = $provideSupabaseForServer();
|
|
28
|
+
const { data: user, error } = await supabase
|
|
29
|
+
.from(await $getTableName('User'))
|
|
30
|
+
.select('*')
|
|
31
|
+
.eq('username', username)
|
|
32
|
+
.single();
|
|
33
|
+
|
|
34
|
+
if (error || !user) {
|
|
35
|
+
// Check if password matches ADMIN_PASSWORD even if user doesn't exist?
|
|
36
|
+
// "The table User should work together with the process.env.ADMIN_PASSWORD"
|
|
37
|
+
// If the user enters a password that matches process.env.ADMIN_PASSWORD, should they get admin access regardless of username?
|
|
38
|
+
// "process.env.ADMIN_PASSWORD is like one of the admin users" implies it's a specific credential.
|
|
39
|
+
// Let's stick to: if username is 'admin' and password is ADMIN_PASSWORD, it works.
|
|
40
|
+
// Or if the password matches ADMIN_PASSWORD, maybe we grant admin access?
|
|
41
|
+
// "Non-admin users can only log in... cannot see list of users"
|
|
42
|
+
|
|
43
|
+
// Re-reading: "process.env.ADMIN_PASSWORD is like one of the admin users in the User table"
|
|
44
|
+
// This suggests it's treated as a user.
|
|
45
|
+
|
|
46
|
+
// If I login with a valid user from DB, I check password hash.
|
|
47
|
+
// If I login with 'admin' and ADMIN_PASSWORD, I get admin.
|
|
48
|
+
|
|
49
|
+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isValid = await verifyPassword(password, (user as AgentsServerDatabase['public']['Tables']['User']['Row']).passwordHash);
|
|
53
|
+
|
|
54
|
+
if (!isValid) {
|
|
55
|
+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await setSession({ username: (user as AgentsServerDatabase['public']['Tables']['User']['Row']).username, isAdmin: (user as AgentsServerDatabase['public']['Tables']['User']['Row']).isAdmin });
|
|
59
|
+
return NextResponse.json({ success: true });
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Login error:', error);
|
|
63
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { $getTableName } from '../../../database/$getTableName';
|
|
2
|
+
import { $provideSupabase } from '../../../database/$provideSupabase';
|
|
3
|
+
import { isUserAdmin } from '../../../utils/isUserAdmin';
|
|
4
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
if (!(await isUserAdmin())) {
|
|
8
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const supabase = $provideSupabase();
|
|
12
|
+
const table = await $getTableName('Metadata');
|
|
13
|
+
|
|
14
|
+
const { data, error } = await supabase.from(table).select('*').order('key');
|
|
15
|
+
|
|
16
|
+
if (error) {
|
|
17
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return NextResponse.json(data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function POST(request: NextRequest) {
|
|
24
|
+
if (!(await isUserAdmin())) {
|
|
25
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const body = await request.json();
|
|
30
|
+
const { key, value, note } = body;
|
|
31
|
+
|
|
32
|
+
if (!key || !value) {
|
|
33
|
+
return NextResponse.json({ error: 'Key and value are required' }, { status: 400 });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const supabase = $provideSupabase();
|
|
37
|
+
const table = await $getTableName('Metadata');
|
|
38
|
+
|
|
39
|
+
const { data, error } = await supabase
|
|
40
|
+
.from(table)
|
|
41
|
+
.insert({ key, value, note })
|
|
42
|
+
.select()
|
|
43
|
+
.single();
|
|
44
|
+
|
|
45
|
+
if (error) {
|
|
46
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return NextResponse.json(data);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function PUT(request: NextRequest) {
|
|
56
|
+
if (!(await isUserAdmin())) {
|
|
57
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const body = await request.json();
|
|
62
|
+
const { key, value, note } = body;
|
|
63
|
+
|
|
64
|
+
if (!key || !value) {
|
|
65
|
+
return NextResponse.json({ error: 'Key and value are required' }, { status: 400 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const supabase = $provideSupabase();
|
|
69
|
+
const table = await $getTableName('Metadata');
|
|
70
|
+
|
|
71
|
+
// Using upsert if it exists or update if strict
|
|
72
|
+
// Since key is unique, upsert works well, but usually PUT implies update.
|
|
73
|
+
// Let's use update to be safe and explicit about editing existing.
|
|
74
|
+
// Actually, for editing, we identify by ID or Key.
|
|
75
|
+
// Let's use Key as identifier since it is unique.
|
|
76
|
+
|
|
77
|
+
const { data, error } = await supabase
|
|
78
|
+
.from(table)
|
|
79
|
+
.update({ value, note, updatedAt: new Date().toISOString() })
|
|
80
|
+
.eq('key', key)
|
|
81
|
+
.select()
|
|
82
|
+
.single();
|
|
83
|
+
|
|
84
|
+
if (error) {
|
|
85
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return NextResponse.json(data);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function DELETE(request: NextRequest) {
|
|
95
|
+
if (!(await isUserAdmin())) {
|
|
96
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const searchParams = request.nextUrl.searchParams;
|
|
100
|
+
const key = searchParams.get('key');
|
|
101
|
+
|
|
102
|
+
if (!key) {
|
|
103
|
+
return NextResponse.json({ error: 'Key is required' }, { status: 400 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const supabase = $provideSupabase();
|
|
107
|
+
const table = await $getTableName('Metadata');
|
|
108
|
+
|
|
109
|
+
const { error } = await supabase.from(table).delete().eq('key', key);
|
|
110
|
+
|
|
111
|
+
if (error) {
|
|
112
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return NextResponse.json({ success: true });
|
|
116
|
+
}
|
|
@@ -4,6 +4,7 @@ import { serializeError } from '@promptbook-local/utils';
|
|
|
4
4
|
import formidable from 'formidable';
|
|
5
5
|
import { readFile } from 'fs/promises';
|
|
6
6
|
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
import { forTime } from 'waitasecond';
|
|
7
8
|
import { assertsError } from '../../../../../../src/errors/assertsError';
|
|
8
9
|
import { string_url } from '../../../../../../src/types/typeAliases';
|
|
9
10
|
import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
|
|
@@ -13,6 +14,9 @@ import { validateMimeType } from '../../../../src/utils/validators/validateMimeT
|
|
|
13
14
|
|
|
14
15
|
export async function POST(request: NextRequest) {
|
|
15
16
|
try {
|
|
17
|
+
await forTime(1);
|
|
18
|
+
// await forTime(5000);
|
|
19
|
+
|
|
16
20
|
const nodeRequest = await nextRequestToNodeRequest(request);
|
|
17
21
|
|
|
18
22
|
const files = await new Promise<formidable.Files>((resolve, reject) => {
|
|
@@ -57,13 +61,13 @@ export async function POST(request: NextRequest) {
|
|
|
57
61
|
return new Response(
|
|
58
62
|
JSON.stringify(
|
|
59
63
|
serializeError(error),
|
|
60
|
-
// <- TODO:
|
|
64
|
+
// <- TODO: [🐱🚀] Rename `serializeError` to `errorToJson`
|
|
61
65
|
null,
|
|
62
66
|
4,
|
|
63
|
-
// <- TODO:
|
|
67
|
+
// <- TODO: [🐱🚀] Allow to configure pretty print for agent server
|
|
64
68
|
),
|
|
65
69
|
{
|
|
66
|
-
status: 400, // <- TODO:
|
|
70
|
+
status: 400, // <- TODO: [🐱🚀] Make `errorToHttpStatusCode`
|
|
67
71
|
headers: { 'Content-Type': 'application/json' },
|
|
68
72
|
},
|
|
69
73
|
);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { $getTableName } from '@/src/database/$getTableName';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { $provideSupabaseForServer } from '../../../../database/$provideSupabaseForServer';
|
|
4
|
+
import { hashPassword } from '../../../../utils/auth';
|
|
5
|
+
import { isUserAdmin } from '../../../../utils/isUserAdmin';
|
|
6
|
+
|
|
7
|
+
export async function PATCH(request: Request, { params }: { params: Promise<{ username: string }> }) {
|
|
8
|
+
if (!(await isUserAdmin())) {
|
|
9
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const { username: usernameParam } = await params;
|
|
14
|
+
const body = await request.json();
|
|
15
|
+
const { password, isAdmin } = body;
|
|
16
|
+
|
|
17
|
+
const updates: { updatedAt: string; passwordHash?: string; isAdmin?: boolean } = {
|
|
18
|
+
updatedAt: new Date().toISOString(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (password) {
|
|
22
|
+
updates.passwordHash = await hashPassword(password);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof isAdmin === 'boolean') {
|
|
26
|
+
updates.isAdmin = isAdmin;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const supabase = $provideSupabaseForServer();
|
|
30
|
+
const { data: updatedUser, error } = await supabase
|
|
31
|
+
.from(await $getTableName('User'))
|
|
32
|
+
.update(updates)
|
|
33
|
+
.eq('username', usernameParam)
|
|
34
|
+
.select('id, username, createdAt, updatedAt, isAdmin')
|
|
35
|
+
.single();
|
|
36
|
+
|
|
37
|
+
if (error) {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!updatedUser) {
|
|
42
|
+
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return NextResponse.json(updatedUser);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Update user error:', error);
|
|
48
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function DELETE(request: Request, { params }: { params: Promise<{ username: string }> }) {
|
|
53
|
+
if (!(await isUserAdmin())) {
|
|
54
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const { username: usernameParam } = await params;
|
|
59
|
+
const supabase = $provideSupabaseForServer();
|
|
60
|
+
|
|
61
|
+
const { error } = await supabase
|
|
62
|
+
.from(await $getTableName('User'))
|
|
63
|
+
.delete()
|
|
64
|
+
.eq('username', usernameParam);
|
|
65
|
+
|
|
66
|
+
if (error) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return NextResponse.json({ success: true });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Delete user error:', error);
|
|
73
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
74
|
+
}
|
|
75
|
+
}
|