@promptbook/cli 0.103.0-48 → 0.103.0-49

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.
Files changed (110) hide show
  1. package/apps/agents-server/README.md +1 -1
  2. package/apps/agents-server/TODO.txt +6 -5
  3. package/apps/agents-server/config.ts +130 -0
  4. package/apps/agents-server/next.config.ts +1 -1
  5. package/apps/agents-server/public/fonts/OpenMoji-black-glyf.woff2 +0 -0
  6. package/apps/agents-server/public/fonts/download-font.js +22 -0
  7. package/apps/agents-server/src/app/[agentName]/[...rest]/page.tsx +6 -0
  8. package/apps/agents-server/src/app/[agentName]/page.tsx +1 -0
  9. package/apps/agents-server/src/app/actions.ts +37 -2
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +68 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +55 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +4 -5
  13. package/apps/agents-server/src/app/agents/[agentName]/CopyField.tsx +44 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +8 -8
  15. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +100 -24
  16. package/apps/agents-server/src/app/agents/[agentName]/api/feedback/route.ts +54 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +6 -6
  18. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -3
  19. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +6 -7
  20. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +4 -5
  21. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +9 -2
  22. package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +23 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/book+chat/{AgentBookAndChatComponent.tsx → AgentBookAndChatComponent.tsx.todo} +4 -4
  24. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +28 -17
  25. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx.todo +21 -0
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatWrapper.tsx +34 -4
  27. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +4 -1
  28. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +42 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +109 -106
  30. package/apps/agents-server/src/app/agents/page.tsx +1 -1
  31. package/apps/agents-server/src/app/api/auth/login/route.ts +65 -0
  32. package/apps/agents-server/src/app/api/auth/logout/route.ts +7 -0
  33. package/apps/agents-server/src/app/api/metadata/route.ts +116 -0
  34. package/apps/agents-server/src/app/api/upload/route.ts +7 -3
  35. package/apps/agents-server/src/app/api/users/[username]/route.ts +75 -0
  36. package/apps/agents-server/src/app/api/users/route.ts +71 -0
  37. package/apps/agents-server/src/app/globals.css +35 -1
  38. package/apps/agents-server/src/app/layout.tsx +43 -23
  39. package/apps/agents-server/src/app/metadata/MetadataClient.tsx +271 -0
  40. package/apps/agents-server/src/app/metadata/page.tsx +13 -0
  41. package/apps/agents-server/src/app/not-found.tsx +5 -0
  42. package/apps/agents-server/src/app/page.tsx +84 -46
  43. package/apps/agents-server/src/components/Auth/AuthControls.tsx +123 -0
  44. package/apps/agents-server/src/components/ErrorPage/ErrorPage.tsx +33 -0
  45. package/apps/agents-server/src/components/ForbiddenPage/ForbiddenPage.tsx +15 -0
  46. package/apps/agents-server/src/components/Header/Header.tsx +146 -0
  47. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +27 -0
  48. package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +40 -0
  49. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +109 -0
  50. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +17 -0
  51. package/apps/agents-server/src/components/UsersList/UsersList.tsx +190 -0
  52. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +60 -0
  53. package/apps/agents-server/src/database/$getTableName.ts +18 -0
  54. package/apps/agents-server/src/database/$provideSupabase.ts +2 -2
  55. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +3 -3
  56. package/apps/agents-server/src/database/getMetadata.ts +31 -0
  57. package/apps/agents-server/src/database/metadataDefaults.ts +32 -0
  58. package/apps/agents-server/src/database/schema.sql +81 -33
  59. package/apps/agents-server/src/database/schema.ts +35 -1
  60. package/apps/agents-server/src/middleware.ts +162 -0
  61. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +11 -7
  62. package/apps/agents-server/src/tools/$provideCdnForServer.ts +1 -1
  63. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +11 -13
  64. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +7 -7
  65. package/apps/agents-server/src/tools/$provideServer.ts +39 -0
  66. package/apps/agents-server/src/utils/auth.ts +33 -0
  67. package/apps/agents-server/src/utils/cdn/utils/nameToSubfolderPath.ts +1 -1
  68. package/apps/agents-server/src/utils/getCurrentUser.ts +32 -0
  69. package/apps/agents-server/src/utils/isIpAllowed.ts +101 -0
  70. package/apps/agents-server/src/utils/isUserAdmin.ts +31 -0
  71. package/apps/agents-server/src/utils/session.ts +50 -0
  72. package/apps/agents-server/tailwind.config.ts +2 -0
  73. package/esm/index.es.js +147 -31
  74. package/esm/index.es.js.map +1 -1
  75. package/esm/typings/servers.d.ts +1 -0
  76. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  77. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  78. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
  79. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  80. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  81. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  82. package/esm/typings/src/commitments/index.d.ts +2 -1
  83. package/esm/typings/src/config.d.ts +1 -0
  84. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  85. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  86. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  87. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  88. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  89. package/esm/typings/src/llm-providers/agent/Agent.d.ts +11 -3
  90. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
  91. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
  92. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
  93. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  94. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  95. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  96. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  97. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  98. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  99. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  100. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  101. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  102. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  103. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  104. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  105. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  106. package/esm/typings/src/version.d.ts +1 -1
  107. package/package.json +1 -1
  108. package/umd/index.umd.js +147 -31
  109. package/umd/index.umd.js.map +1 -1
  110. package/apps/agents-server/config.ts.todo +0 -38
@@ -2,6 +2,40 @@
2
2
  @import 'tailwindcss/components';
3
3
  @import 'tailwindcss/utilities';
4
4
 
5
+ /**
6
+ * OpenMoji black and white CSS
7
+ *
8
+ * https://github.com/hfg-gmuend/openmoji/blob/master/font/OpenMoji-black-glyf/openmoji.css
9
+ */
10
+ @font-face {
11
+ font-family: 'OpenMojiBlack';
12
+ src: url('/fonts/OpenMoji-black-glyf.woff2') format('woff2');
13
+ unicode-range: U+23, U+2A, U+2D, U+30-39, U+A9, U+AE, U+200D, U+203C, U+2049, U+20E3, U+2117, U+2120, U+2122,
14
+ U+2139, U+2194-2199, U+21A9, U+21AA, U+229C, U+231A, U+231B, U+2328, U+23CF, U+23E9-23F3, U+23F8-23FE, U+24C2,
15
+ U+25A1, U+25AA-25AE, U+25B6, U+25C0, U+25C9, U+25D0, U+25D1, U+25E7-25EA, U+25ED, U+25EE, U+25FB-25FE,
16
+ U+2600-2605, U+260E, U+2611, U+2614, U+2615, U+2618, U+261D, U+2620, U+2622, U+2623, U+2626, U+262A, U+262E,
17
+ U+262F, U+2638-263A, U+2640, U+2642, U+2648-2653, U+265F, U+2660, U+2663, U+2665, U+2666, U+2668, U+267B,
18
+ U+267E, U+267F, U+2691-2697, U+2699, U+269B, U+269C, U+26A0, U+26A1, U+26A7, U+26AA, U+26AB, U+26B0, U+26B1,
19
+ U+26BD, U+26BE, U+26C4, U+26C5, U+26C8, U+26CE, U+26CF, U+26D1, U+26D3, U+26D4, U+26E9, U+26EA, U+26F0-26F5,
20
+ U+26F7-26FA, U+26FD, U+2702, U+2705, U+2708-270D, U+270F, U+2712, U+2714, U+2716, U+271D, U+2721, U+2728,
21
+ U+2733, U+2734, U+2744, U+2747, U+274C, U+274E, U+2753-2755, U+2757, U+2763, U+2764, U+2795-2797, U+27A1,
22
+ U+27B0, U+27BF, U+2934, U+2935, U+2B05-2B07, U+2B0C, U+2B0D, U+2B1B, U+2B1C, U+2B1F-2B24, U+2B2E, U+2B2F,
23
+ U+2B50, U+2B55, U+2B58, U+2B8F, U+2BBA-2BBC, U+2BC3, U+2BC4, U+2BEA, U+2BEB, U+3030, U+303D, U+3297, U+3299,
24
+ U+E000-E009, U+E010, U+E011, U+E040-E06D, U+E080-E0B4, U+E0C0-E0CC, U+E0FF-E10D, U+E140-E14A, U+E150-E157,
25
+ U+E181-E189, U+E1C0-E1C4, U+E1C6-E1D9, U+E200-E216, U+E240-E269, U+E280-E283, U+E2C0-E2C4, U+E2C6-E2DA,
26
+ U+E300-E303, U+E305-E30F, U+E312-E316, U+E318-E322, U+E324-E329, U+E32B, U+E340-E348, U+E380, U+E381, U+F000,
27
+ U+F77A, U+F8FF, U+FE0F, U+1F004, U+1F0CF, U+1F10D-1F10F, U+1F12F, U+1F16D-1F171, U+1F17E, U+1F17F, U+1F18E,
28
+ U+1F191-1F19A, U+1F1E6-1F1FF, U+1F201, U+1F202, U+1F21A, U+1F22F, U+1F232-1F23A, U+1F250, U+1F251,
29
+ U+1F260-1F265, U+1F300-1F321, U+1F324-1F393, U+1F396, U+1F397, U+1F399-1F39B, U+1F39E-1F3F0, U+1F3F3-1F3F5,
30
+ U+1F3F7-1F4FD, U+1F4FF-1F53D, U+1F549-1F54E, U+1F550-1F567, U+1F56F, U+1F570, U+1F573-1F57A, U+1F587,
31
+ U+1F58A-1F58D, U+1F590, U+1F595, U+1F596, U+1F5A4, U+1F5A5, U+1F5A8, U+1F5B1, U+1F5B2, U+1F5BC, U+1F5C2-1F5C4,
32
+ U+1F5D1-1F5D3, U+1F5DC-1F5DE, U+1F5E1, U+1F5E3, U+1F5E8, U+1F5EF, U+1F5F3, U+1F5FA-1F64F, U+1F680-1F6C5,
33
+ U+1F6CB-1F6D2, U+1F6D5-1F6D7, U+1F6DC-1F6E5, U+1F6E9, U+1F6EB, U+1F6EC, U+1F6F0, U+1F6F3-1F6FC, U+1F7E0-1F7EB,
34
+ U+1F7F0, U+1F90C-1F93A, U+1F93C-1F945, U+1F947-1F9FF, U+1FA70-1FA7C, U+1FA80-1FA88, U+1FA90-1FABD,
35
+ U+1FABF-1FAC5, U+1FACE-1FADB, U+1FAE0-1FAE8, U+1FAF0-1FAF8, U+1FBC5-1FBC9, U+E0061-E0067, U+E0069,
36
+ U+E006C-E0079, U+E007F;
37
+ }
38
+
5
39
  :root {
6
40
  --background: #ffffff;
7
41
  --foreground: #171717;
@@ -17,7 +51,7 @@
17
51
  body {
18
52
  background: var(--background);
19
53
  color: var(--foreground);
20
- font-family: 'Barlow Condensed', Arial, Helvetica, sans-serif;
54
+ font-family: var(--font-barlow-condensed), 'OpenMojiBlack', Arial, Helvetica, sans-serif;
21
55
  }
22
56
 
23
57
  /* Custom utilities */
@@ -1,24 +1,33 @@
1
1
  import faviconLogoImage from '@/public/favicon.ico';
2
+ import { LayoutWrapper } from '@/src/components/LayoutWrapper/LayoutWrapper';
2
3
  import type { Metadata } from 'next';
3
4
  import { Barlow_Condensed } from 'next/font/google';
5
+ import { getMetadata } from '../database/getMetadata';
6
+ import { isUserAdmin } from '../utils/isUserAdmin';
4
7
  import './globals.css';
5
8
 
6
9
  const barlowCondensed = Barlow_Condensed({
7
10
  subsets: ['latin'],
8
11
  weight: ['300', '400', '500', '600', '700'],
12
+ variable: '--font-barlow-condensed',
9
13
  });
10
14
 
11
- export const metadata: Metadata = {
12
- title: 'Promptbook agents server',
13
- description: '@@@',
14
- keywords: ['@@@'],
15
- authors: [{ name: 'Promptbook Team' }],
16
- openGraph: {
17
- title: 'Promptbook agents server',
18
- description: '@@@',
19
- type: 'website',
20
- images: [
21
- /*
15
+ export async function generateMetadata(): Promise<Metadata> {
16
+ const serverName = (await getMetadata('SERVER_NAME')) || 'Promptbook Agents Server';
17
+ const serverDescription = (await getMetadata('SERVER_DESCRIPTION')) || 'Agents server powered by Promptbook';
18
+ const serverUrl = (await getMetadata('SERVER_URL')) || 'https://ptbk.io';
19
+
20
+ return {
21
+ title: serverName,
22
+ description: serverDescription,
23
+ // TODO: keywords: ['@@@'],
24
+ authors: [{ name: 'Promptbook Team' }],
25
+ openGraph: {
26
+ title: serverName,
27
+ description: serverDescription,
28
+ type: 'website',
29
+ images: [
30
+ /*
22
31
  TODO:
23
32
  {
24
33
  url: 'https://www.ptbk.io/design',
@@ -27,21 +36,28 @@ export const metadata: Metadata = {
27
36
  alt: 'Promptbook agents server',
28
37
  },
29
38
  */
30
- ],
31
- },
32
- twitter: {
33
- card: 'summary_large_image',
34
- title: 'Promptbook agents server',
35
- description: '@@@',
36
- // TODO: images: ['https://www.ptbk.io/design'],
37
- },
38
- };
39
+ ],
40
+ },
41
+ twitter: {
42
+ card: 'summary_large_image',
43
+ title: serverName,
44
+ description: serverDescription,
45
+ // TODO: images: ['https://www.ptbk.io/design'],
46
+ },
47
+ metadataBase: new URL(serverUrl),
48
+ };
49
+ }
39
50
 
40
- export default function RootLayout({
51
+ export default async function RootLayout({
41
52
  children,
42
53
  }: Readonly<{
43
54
  children: React.ReactNode;
44
55
  }>) {
56
+ const isAdmin = await isUserAdmin();
57
+ const serverName = (await getMetadata('SERVER_NAME')) || 'Promptbook Agents Server';
58
+ const serverLogoUrl = (await getMetadata('SERVER_LOGO_URL')) || null;
59
+ const serverFaviconUrl = (await getMetadata('SERVER_FAVICON_URL')) || faviconLogoImage.src;
60
+
45
61
  return (
46
62
  <html lang="en">
47
63
  <head>
@@ -64,9 +80,13 @@ export default function RootLayout({
64
80
  />
65
81
  */}
66
82
  {/* Default favicon as a fallback */}
67
- <link rel="icon" href={faviconLogoImage.src} type="image/x-icon" />
83
+ <link rel="icon" href={serverFaviconUrl} type="image/x-icon" />
68
84
  </head>
69
- <body className={`${barlowCondensed.className} antialiased bg-white text-gray-900`}>{children}</body>
85
+ <body className={`${barlowCondensed.variable} antialiased bg-white text-gray-900`}>
86
+ <LayoutWrapper isAdmin={isAdmin} serverName={serverName} serverLogoUrl={serverLogoUrl}>
87
+ {children}
88
+ </LayoutWrapper>
89
+ </body>
70
90
  </html>
71
91
  );
72
92
  }
@@ -0,0 +1,271 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { metadataDefaults } from '../../database/metadataDefaults';
5
+
6
+ type MetadataEntry = {
7
+ id: number;
8
+ key: string;
9
+ value: string;
10
+ note: string | null;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ isDefault?: boolean;
14
+ };
15
+
16
+ export function MetadataClient() {
17
+ const [metadata, setMetadata] = useState<MetadataEntry[]>([]);
18
+ const [loading, setLoading] = useState(true);
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [editingId, setEditingId] = useState<number | null>(null);
21
+ const [formState, setFormState] = useState<{
22
+ key: string;
23
+ value: string;
24
+ note: string;
25
+ }>({ key: '', value: '', note: '' });
26
+
27
+ const fetchMetadata = async () => {
28
+ try {
29
+ setLoading(true);
30
+ const response = await fetch('/api/metadata');
31
+ if (!response.ok) {
32
+ throw new Error('Failed to fetch metadata');
33
+ }
34
+ const data: MetadataEntry[] = await response.json();
35
+
36
+ // Merge defaults
37
+ const mergedData = [...data];
38
+ for (const def of metadataDefaults) {
39
+ if (!mergedData.find((m) => m.key === def.key)) {
40
+ mergedData.push({
41
+ id: -1,
42
+ key: def.key,
43
+ value: def.value,
44
+ note: def.note,
45
+ createdAt: new Date().toISOString(),
46
+ updatedAt: new Date().toISOString(),
47
+ isDefault: true,
48
+ });
49
+ }
50
+ }
51
+ // Sort by key
52
+ mergedData.sort((a, b) => a.key.localeCompare(b.key));
53
+
54
+ setMetadata(mergedData);
55
+ } catch (err) {
56
+ setError(err instanceof Error ? err.message : 'An error occurred');
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+
62
+ useEffect(() => {
63
+ fetchMetadata();
64
+ }, []);
65
+
66
+ const handleSubmit = async (e: React.FormEvent) => {
67
+ e.preventDefault();
68
+ setError(null);
69
+
70
+ try {
71
+ // If editingId is -1 (default value) or null (new value), use POST to create
72
+ // If editingId is > 0 (existing value), use PUT to update
73
+ const method = editingId && editingId !== -1 ? 'PUT' : 'POST';
74
+ const response = await fetch('/api/metadata', {
75
+ method,
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify(formState),
78
+ });
79
+
80
+ if (!response.ok) {
81
+ const data = await response.json();
82
+ throw new Error(data.error || 'Failed to save metadata');
83
+ }
84
+
85
+ setFormState({ key: '', value: '', note: '' });
86
+ setEditingId(null);
87
+ fetchMetadata();
88
+ } catch (err) {
89
+ setError(err instanceof Error ? err.message : 'An error occurred');
90
+ }
91
+ };
92
+
93
+ const handleEdit = (entry: MetadataEntry) => {
94
+ setEditingId(entry.id);
95
+ setFormState({
96
+ key: entry.key,
97
+ value: entry.value,
98
+ note: entry.note || '',
99
+ });
100
+ };
101
+
102
+ const handleDelete = async (key: string) => {
103
+ if (!confirm('Are you sure you want to delete this metadata?')) return;
104
+
105
+ try {
106
+ const response = await fetch(`/api/metadata?key=${encodeURIComponent(key)}`, {
107
+ method: 'DELETE',
108
+ });
109
+
110
+ if (!response.ok) {
111
+ const data = await response.json();
112
+ throw new Error(data.error || 'Failed to delete metadata');
113
+ }
114
+
115
+ fetchMetadata();
116
+ } catch (err) {
117
+ setError(err instanceof Error ? err.message : 'An error occurred');
118
+ }
119
+ };
120
+
121
+ const handleCancel = () => {
122
+ setEditingId(null);
123
+ setFormState({ key: '', value: '', note: '' });
124
+ };
125
+
126
+ if (loading && metadata.length === 0) {
127
+ return <div className="p-8 text-center">Loading metadata...</div>;
128
+ }
129
+
130
+ return (
131
+ <div className="container mx-auto p-8 max-w-4xl">
132
+ <h1 className="text-3xl font-bold mb-8">Metadata Management</h1>
133
+
134
+ {error && (
135
+ <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">
136
+ {error}
137
+ </div>
138
+ )}
139
+
140
+ <div className="bg-white shadow rounded-lg p-6 mb-8">
141
+ <h2 className="text-xl font-semibold mb-4">
142
+ {editingId ? 'Edit Metadata' : 'Add New Metadata'}
143
+ </h2>
144
+ <form onSubmit={handleSubmit} className="space-y-4">
145
+ <div>
146
+ <label htmlFor="key" className="block text-sm font-medium text-gray-700 mb-1">
147
+ Key
148
+ </label>
149
+ <input
150
+ type="text"
151
+ id="key"
152
+ value={formState.key}
153
+ onChange={(e) => setFormState({ ...formState, key: e.target.value })}
154
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
155
+ required
156
+ disabled={!!editingId} // Key cannot be changed during edit
157
+ placeholder="e.g., SERVER_NAME"
158
+ />
159
+ {editingId && <p className="text-xs text-gray-500 mt-1">Key cannot be changed.</p>}
160
+ </div>
161
+ <div>
162
+ <label htmlFor="value" className="block text-sm font-medium text-gray-700 mb-1">
163
+ Value
164
+ </label>
165
+ <textarea
166
+ id="value"
167
+ value={formState.value}
168
+ onChange={(e) => setFormState({ ...formState, value: e.target.value })}
169
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[100px]"
170
+ required
171
+ placeholder="Metadata value..."
172
+ />
173
+ </div>
174
+ <div>
175
+ <label htmlFor="note" className="block text-sm font-medium text-gray-700 mb-1">
176
+ Note (Optional)
177
+ </label>
178
+ <input
179
+ type="text"
180
+ id="note"
181
+ value={formState.note}
182
+ onChange={(e) => setFormState({ ...formState, note: e.target.value })}
183
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
184
+ placeholder="Description of this metadata"
185
+ />
186
+ </div>
187
+ <div className="flex space-x-3">
188
+ <button
189
+ type="submit"
190
+ className="bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors"
191
+ >
192
+ {editingId ? 'Update Metadata' : 'Add Metadata'}
193
+ </button>
194
+ {editingId && (
195
+ <button
196
+ type="button"
197
+ onClick={handleCancel}
198
+ className="bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300 transition-colors"
199
+ >
200
+ Cancel
201
+ </button>
202
+ )}
203
+ </div>
204
+ </form>
205
+ </div>
206
+
207
+ <div className="bg-white shadow rounded-lg overflow-hidden">
208
+ <table className="min-w-full divide-y divide-gray-200">
209
+ <thead className="bg-gray-50">
210
+ <tr>
211
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
212
+ Key
213
+ </th>
214
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
215
+ Value
216
+ </th>
217
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
218
+ Note
219
+ </th>
220
+ <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
221
+ Actions
222
+ </th>
223
+ </tr>
224
+ </thead>
225
+ <tbody className="bg-white divide-y divide-gray-200">
226
+ {metadata.length === 0 ? (
227
+ <tr>
228
+ <td colSpan={4} className="px-6 py-4 text-center text-gray-500">
229
+ No metadata found.
230
+ </td>
231
+ </tr>
232
+ ) : (
233
+ metadata.map((entry) => (
234
+ <tr key={entry.id}>
235
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
236
+ {entry.key}
237
+ </td>
238
+ <td className="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">
239
+ {entry.value}
240
+ </td>
241
+ <td className="px-6 py-4 text-sm text-gray-500">
242
+ {entry.note || '-'}
243
+ </td>
244
+ <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
245
+ <button
246
+ onClick={() => handleEdit(entry)}
247
+ className="text-blue-600 hover:text-blue-900 mr-4"
248
+ >
249
+ Edit
250
+ </button>
251
+ {!entry.isDefault && (
252
+ <button
253
+ onClick={() => handleDelete(entry.key)}
254
+ className="text-red-600 hover:text-red-900"
255
+ >
256
+ Delete
257
+ </button>
258
+ )}
259
+ {entry.isDefault && (
260
+ <span className="text-gray-400 text-xs italic ml-2">Default</span>
261
+ )}
262
+ </td>
263
+ </tr>
264
+ ))
265
+ )}
266
+ </tbody>
267
+ </table>
268
+ </div>
269
+ </div>
270
+ );
271
+ }
@@ -0,0 +1,13 @@
1
+ import { ForbiddenPage } from '../../components/ForbiddenPage/ForbiddenPage';
2
+ import { isUserAdmin } from '../../utils/isUserAdmin';
3
+ import { MetadataClient } from './MetadataClient';
4
+
5
+ export default async function MetadataPage() {
6
+ const isAdmin = await isUserAdmin();
7
+
8
+ if (!isAdmin) {
9
+ return <ForbiddenPage />;
10
+ }
11
+
12
+ return <MetadataClient />;
13
+ }
@@ -0,0 +1,5 @@
1
+ import { NotFoundPage } from '../components/NotFoundPage/NotFoundPage';
2
+
3
+ export default function NotFound() {
4
+ return <NotFoundPage />;
5
+ }
@@ -1,17 +1,21 @@
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';
12
13
  import { getLongRunningTask } from '../deamons/longRunningTask';
13
14
  import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
14
15
  import { $provideExecutionToolsForServer } from '../tools/$provideExecutionToolsForServer';
16
+ import { $provideServer } from '../tools/$provideServer';
17
+ import { getCurrentUser } from '../utils/getCurrentUser';
18
+ import { isUserAdmin } from '../utils/isUserAdmin';
15
19
  import { AddAgentButton } from './AddAgentButton';
16
20
 
17
21
  // Add calendar formats that include seconds
@@ -27,6 +31,9 @@ const calendarWithSeconds = {
27
31
  export default async function HomePage() {
28
32
  $sideEffect(/* Note: [🐶] This will ensure dynamic rendering of page and avoid Next.js pre-render */ headers());
29
33
 
34
+ const isAdmin = await isUserAdmin(); /* <- TODO: [👹] Here should be user permissions */
35
+ const currentUser = await getCurrentUser();
36
+
30
37
  const collection = await $provideAgentCollectionForServer();
31
38
  const agents = await collection.listAgents();
32
39
 
@@ -35,19 +42,20 @@ export default async function HomePage() {
35
42
  const executionTools = await $provideExecutionToolsForServer();
36
43
  const models = await getSingleLlmExecutionTools(executionTools.llm).listModels();
37
44
 
45
+ const host = (await headers()).get('host');
46
+
38
47
  return (
39
48
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
40
49
  <div className="container mx-auto px-4 py-16">
41
- <h1 className="text-4xl font-bold text-gray-900 mb-4">
42
- <Image src={logoImage} alt="Promptbook Logo" height={50} className="inline-block mr-4" />
43
- Promptbook Agents Server
44
- </h1>
50
+ <div className="flex justify-end mb-4">
51
+ <AuthControls initialUser={currentUser} />
52
+ </div>
45
53
 
46
54
  <>
47
- <h2 className="text-3xl text-gray-900 mt-16 mb-4">Agents ({agents.length})</h2>
55
+ <h2 className="text-3xl text-gray-900 mt-4 mb-4">Agents ({agents.length})</h2>
48
56
  <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
49
57
  {agents.map((agent) => (
50
- <Link key={agent.agentName} href={`/agents/${agent.agentName}`}>
58
+ <Link key={agent.agentName} href={`/${agent.agentName}`}>
51
59
  <AvatarProfile
52
60
  {...{ agent }}
53
61
  style={
@@ -61,50 +69,80 @@ export default async function HomePage() {
61
69
  />
62
70
  </Link>
63
71
  ))}
64
- <AddAgentButton />
72
+ {isAdmin && <AddAgentButton />}
65
73
  </div>
66
74
  </>
67
75
 
68
- <>
69
- <h2 className="text-3xl text-gray-900 mt-16 mb-4">Models ({models.length})</h2>
70
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
71
- {models.map(({ modelName, modelTitle, modelDescription }) => (
72
- <Link key={modelName} href={`#!!!`}>
73
- <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">
74
- <h2 className="text-2xl font-semibold text-gray-900 mb-2">{modelTitle}</h2>
75
- <code>{modelName}</code>
76
- <p className="text-gray-600">{modelDescription}</p>
77
- </div>
76
+ {isAdmin && <UsersList />}
77
+
78
+ {isAdmin && (
79
+ <>
80
+ <h2 className="text-3xl text-gray-900 mt-16 mb-4">Models ({models.length})</h2>
81
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
82
+ {models.map(({ modelName, modelTitle, modelDescription }) => (
83
+ <Link key={modelName} href={`#[🐱‍🚀]`}>
84
+ <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">
85
+ <h2 className="text-2xl font-semibold text-gray-900 mb-2">{modelTitle}</h2>
86
+ <code>{modelName}</code>
87
+ <p className="text-gray-600">{modelDescription}</p>
88
+ </div>
89
+ </Link>
90
+ ))}
91
+ </div>
92
+ </>
93
+ )}
94
+
95
+ {isAdmin && (
96
+ <>
97
+ {/* Note: Shown in <AboutPromptbookInformation />: <h2 className="text-3xl text-gray-900 mt-16 mb-4">About Promptbook</h2> */}
98
+ <AboutPromptbookInformation />
99
+ </>
100
+ )}
101
+
102
+ {isAdmin && (
103
+ <>
104
+ <h2 className="text-3xl text-gray-900 mt-16 mb-4">Technical Information</h2>
105
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
106
+ <Link
107
+ href={'#'}
108
+ 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"
109
+ >
110
+ <h2 className="text-2xl font-semibold text-gray-900 mb-2">
111
+ Long running task {longRunningTask.taskId}
112
+ </h2>
113
+ <p className="text-gray-600">Tick: {longRunningTask.tick}</p>
114
+ <p className="text-gray-600">
115
+ Created At:{' '}
116
+ {moment(longRunningTask.createdAt).calendar(undefined, calendarWithSeconds)}
117
+ </p>
118
+ <p className="text-gray-600">
119
+ Updated At:{' '}
120
+ {moment(longRunningTask.updatedAt).calendar(undefined, calendarWithSeconds)}
121
+ </p>
78
122
  </Link>
79
- ))}
80
- </div>
81
- </>
82
123
 
83
- <>
84
- <h2 className="text-3xl text-gray-900 mt-16 mb-4">About</h2>
85
- <AboutPromptbookInformation />
86
- </>
124
+ <VercelDeploymentCard />
87
125
 
88
- <>
89
- <h2 className="text-3xl text-gray-900 mt-16 mb-4">Technical Information</h2>
90
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
91
- <Link
92
- href={'#'}
93
- 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"
94
- >
95
- <h2 className="text-2xl font-semibold text-gray-900 mb-2">
96
- Long running task {longRunningTask.taskId}
97
- </h2>
98
- <p className="text-gray-600">Tick: {longRunningTask.tick}</p>
99
- <p className="text-gray-600">
100
- Created At: {moment(longRunningTask.createdAt).calendar(undefined, calendarWithSeconds)}
101
- </p>
102
- <p className="text-gray-600">
103
- Updated At: {moment(longRunningTask.updatedAt).calendar(undefined, calendarWithSeconds)}
104
- </p>
105
- </Link>
106
- </div>
107
- </>
126
+ <Link
127
+ href={'#'}
128
+ 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"
129
+ >
130
+ <h2 className="text-2xl font-semibold text-gray-900 mb-2">HTTP Information</h2>
131
+
132
+ <p className="text-gray-600">Host: {host}</p>
133
+ </Link>
134
+
135
+ <Link
136
+ href={'#'}
137
+ 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"
138
+ >
139
+ <h2 className="text-2xl font-semibold text-gray-900 mb-2">Server</h2>
140
+
141
+ <pre>{JSON.stringify(await $provideServer(), null, 2)}</pre>
142
+ </Link>
143
+ </div>
144
+ </>
145
+ )}
108
146
  </div>
109
147
  </div>
110
148
  );