@promptbook/cli 0.103.0-52 → 0.103.0-54
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/config.ts +3 -3
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/sw.js +16 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +24 -4
- package/apps/agents-server/src/app/actions.ts +15 -13
- package/apps/agents-server/src/app/admin/api-tokens/ApiTokensClient.tsx +186 -0
- package/apps/agents-server/src/app/admin/api-tokens/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +541 -0
- package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +532 -0
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +241 -27
- package/apps/agents-server/src/app/admin/models/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +131 -0
- package/apps/agents-server/src/app/admin/users/[userId]/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/users/page.tsx +18 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +10 -2
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatFeedbackButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatHistoryButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +41 -0
- package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +74 -0
- package/apps/agents-server/src/app/agents/[agentName]/ServiceWorkerRegister.tsx +24 -0
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +19 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +67 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/openai/chat/completions/route.ts +176 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +177 -0
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +53 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +11 -11
- package/apps/agents-server/src/app/agents/[agentName]/history/RestoreVersionButton.tsx +46 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +12 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +62 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +80 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +61 -0
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +102 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +64 -24
- package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +47 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +19 -0
- package/apps/agents-server/src/app/api/agents/route.ts +22 -13
- package/apps/agents-server/src/app/api/api-tokens/route.ts +76 -0
- package/apps/agents-server/src/app/api/auth/login/route.ts +6 -44
- package/apps/agents-server/src/app/api/chat-feedback/[id]/route.ts +38 -0
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +157 -0
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +37 -0
- package/apps/agents-server/src/app/api/chat-history/route.ts +147 -0
- package/apps/agents-server/src/app/api/federated-agents/route.ts +17 -0
- package/apps/agents-server/src/app/api/upload/route.ts +9 -1
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +63 -0
- package/apps/agents-server/src/app/docs/page.tsx +34 -0
- package/apps/agents-server/src/app/layout.tsx +29 -3
- package/apps/agents-server/src/app/manifest.ts +109 -0
- package/apps/agents-server/src/app/page.tsx +8 -45
- package/apps/agents-server/src/app/recycle-bin/RestoreAgentButton.tsx +40 -0
- package/apps/agents-server/src/app/recycle-bin/actions.ts +27 -0
- package/apps/agents-server/src/app/recycle-bin/page.tsx +58 -0
- package/apps/agents-server/src/app/restricted/page.tsx +33 -0
- package/apps/agents-server/src/app/test/og-image/README.md +1 -0
- package/apps/agents-server/src/app/test/og-image/opengraph-image.tsx +37 -0
- package/apps/agents-server/src/app/test/og-image/page.tsx +22 -0
- package/apps/agents-server/src/components/Footer/Footer.tsx +175 -0
- package/apps/agents-server/src/components/Header/Header.tsx +450 -79
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +46 -14
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +58 -0
- package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +21 -0
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +183 -0
- package/apps/agents-server/src/components/Homepage/ModelsSection.tsx +75 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +29 -3
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +18 -17
- package/apps/agents-server/src/components/Portal/Portal.tsx +38 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +82 -131
- package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +139 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +38 -6
- package/apps/agents-server/src/database/migrations/2025-12-0010-llm-cache.sql +12 -0
- package/apps/agents-server/src/database/migrations/2025-12-0060-api-tokens.sql +13 -0
- package/apps/agents-server/src/database/schema.ts +51 -0
- package/apps/agents-server/src/middleware.ts +193 -92
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +3 -7
- package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +10 -1
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/utils/authenticateUser.ts +42 -0
- package/apps/agents-server/src/utils/cache/SupabaseCacheStorage.ts +55 -0
- package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +63 -0
- package/apps/agents-server/src/utils/chatFeedbackAdmin.ts +96 -0
- package/apps/agents-server/src/utils/chatHistoryAdmin.ts +96 -0
- package/apps/agents-server/src/utils/getEffectiveFederatedServers.ts +22 -0
- package/apps/agents-server/src/utils/getFederatedAgents.ts +31 -8
- package/apps/agents-server/src/utils/getFederatedServersFromMetadata.ts +10 -0
- package/apps/agents-server/src/utils/getVisibleCommitmentDefinitions.ts +12 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +2 -2
- package/apps/agents-server/vercel.json +7 -0
- package/esm/index.es.js +279 -2
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +8 -1
- package/esm/typings/src/_packages/components.index.d.ts +2 -0
- package/esm/typings/src/_packages/core.index.d.ts +6 -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/AgentModelRequirements.d.ts +7 -0
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +4 -0
- package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +12 -0
- package/esm/typings/src/book-components/icons/MicIcon.d.ts +8 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +17 -0
- package/esm/typings/src/commitments/ACTION/ACTION.d.ts +4 -0
- package/esm/typings/src/commitments/DELETE/DELETE.d.ts +4 -0
- package/esm/typings/src/commitments/FORMAT/FORMAT.d.ts +4 -0
- package/esm/typings/src/commitments/GOAL/GOAL.d.ts +4 -0
- package/esm/typings/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +4 -0
- package/esm/typings/src/commitments/MEMORY/MEMORY.d.ts +4 -0
- package/esm/typings/src/commitments/MESSAGE/AgentMessageCommitmentDefinition.d.ts +32 -0
- package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +4 -0
- package/esm/typings/src/commitments/MESSAGE/MESSAGE.d.ts +4 -0
- package/esm/typings/src/commitments/MESSAGE/UserMessageCommitmentDefinition.d.ts +32 -0
- package/esm/typings/src/commitments/META/META.d.ts +4 -0
- package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +4 -0
- package/esm/typings/src/commitments/META_IMAGE/META_IMAGE.d.ts +4 -0
- package/esm/typings/src/commitments/META_LINK/META_LINK.d.ts +4 -0
- package/esm/typings/src/commitments/MODEL/MODEL.d.ts +4 -0
- package/esm/typings/src/commitments/NOTE/NOTE.d.ts +4 -0
- package/esm/typings/src/commitments/PERSONA/PERSONA.d.ts +4 -0
- package/esm/typings/src/commitments/RULE/RULE.d.ts +4 -0
- package/esm/typings/src/commitments/SAMPLE/SAMPLE.d.ts +4 -0
- package/esm/typings/src/commitments/SCENARIO/SCENARIO.d.ts +4 -0
- package/esm/typings/src/commitments/STYLE/STYLE.d.ts +4 -0
- package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +5 -0
- package/esm/typings/src/commitments/_base/CommitmentDefinition.d.ts +5 -0
- package/esm/typings/src/commitments/_base/NotYetImplementedCommitmentDefinition.d.ts +4 -0
- package/esm/typings/src/commitments/index.d.ts +20 -1
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +9 -0
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +2 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +10 -1
- package/esm/typings/src/utils/normalization/normalizeMessageText.d.ts +9 -0
- package/esm/typings/src/utils/normalization/normalizeMessageText.test.d.ts +1 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +279 -2
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +0 -119
|
@@ -75,114 +75,215 @@ export async function middleware(req: NextRequest) {
|
|
|
75
75
|
const allowedIps =
|
|
76
76
|
allowedIpsMetadata !== null && allowedIpsMetadata !== undefined ? allowedIpsMetadata : allowedIpsEnv;
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// to avoid CORS issues ("Redirect is not allowed for a preflight request")
|
|
81
|
-
if (req.method === 'OPTIONS') {
|
|
82
|
-
return new NextResponse(null, {
|
|
83
|
-
status: 200,
|
|
84
|
-
headers: {
|
|
85
|
-
'Access-Control-Allow-Origin': '*',
|
|
86
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
87
|
-
'Access-Control-Allow-Headers': 'Content-Type',
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
78
|
+
let isValidToken = false;
|
|
79
|
+
const authHeader = req.headers.get('authorization');
|
|
91
80
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const pathParts = req.nextUrl.pathname.split('/');
|
|
95
|
-
const potentialAgentName = pathParts[1];
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
potentialAgentName &&
|
|
99
|
-
!['agents', 'api', 'admin', 'embed', '_next', 'favicon.ico'].includes(potentialAgentName) &&
|
|
100
|
-
!potentialAgentName.startsWith('.') &&
|
|
101
|
-
// Note: Other static files are excluded by the matcher configuration below
|
|
102
|
-
true
|
|
103
|
-
) {
|
|
104
|
-
const url = req.nextUrl.clone();
|
|
105
|
-
url.pathname = `/agents${req.nextUrl.pathname}`;
|
|
106
|
-
const response = NextResponse.redirect(url);
|
|
107
|
-
|
|
108
|
-
// Enable CORS for the redirect
|
|
109
|
-
response.headers.set('Access-Control-Allow-Origin', '*');
|
|
110
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
111
|
-
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
|
81
|
+
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
82
|
+
const token = authHeader.split(' ')[1];
|
|
112
83
|
|
|
113
|
-
|
|
114
|
-
|
|
84
|
+
if (token.startsWith('ptbk_')) {
|
|
85
|
+
const host = req.headers.get('host');
|
|
86
|
+
let tablePrefix = SUPABASE_TABLE_PREFIX;
|
|
115
87
|
|
|
116
|
-
|
|
117
|
-
|
|
88
|
+
if (host && SERVERS && SERVERS.length > 0) {
|
|
89
|
+
if (SERVERS.some((server) => server === host)) {
|
|
90
|
+
let serverName = host;
|
|
91
|
+
serverName = serverName.replace(/\.ptbk\.io$/, '');
|
|
92
|
+
serverName = normalizeTo_PascalCase(serverName);
|
|
93
|
+
tablePrefix = `server_${serverName}_`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
118
96
|
|
|
119
|
-
if (host && SERVERS && !SERVERS.some((server) => server === host)) {
|
|
120
97
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
121
98
|
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
122
99
|
|
|
123
100
|
if (supabaseUrl && supabaseKey) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
101
|
+
try {
|
|
102
|
+
const supabase = createClient(supabaseUrl, supabaseKey, {
|
|
103
|
+
auth: {
|
|
104
|
+
persistSession: false,
|
|
105
|
+
autoRefreshToken: false,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const { data } = await supabase
|
|
110
|
+
.from(`${tablePrefix}ApiTokens`)
|
|
111
|
+
.select('id')
|
|
112
|
+
.eq('token', token)
|
|
113
|
+
.eq('isRevoked', false)
|
|
114
|
+
.single();
|
|
115
|
+
|
|
116
|
+
if (data) {
|
|
117
|
+
isValidToken = true;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Error validating token in middleware:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
const isIpAllowedResult = isIpAllowed(ip, allowedIps);
|
|
127
|
+
const isLoggedIn = req.cookies.has('sessionToken');
|
|
128
|
+
const isAccessRestricted = !isIpAllowedResult && !isLoggedIn && !isValidToken;
|
|
129
|
+
|
|
130
|
+
// Handle OPTIONS (preflight) requests globally
|
|
131
|
+
if (req.method === 'OPTIONS') {
|
|
132
|
+
return new NextResponse(null, {
|
|
133
|
+
status: 200,
|
|
134
|
+
headers: {
|
|
135
|
+
'Access-Control-Allow-Origin': '*',
|
|
136
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
137
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
142
|
+
if (isAccessRestricted) {
|
|
143
|
+
const path = req.nextUrl.pathname;
|
|
144
|
+
|
|
145
|
+
// Allow specific paths for restricted users
|
|
146
|
+
// - /: Homepage / Agent List
|
|
147
|
+
// - /agents: Agent List
|
|
148
|
+
// - /api/agents: Agent List API
|
|
149
|
+
// - /api/federated-agents: Federated Agent List API
|
|
150
|
+
// - /api/auth/*: Auth endpoints
|
|
151
|
+
// - /restricted: Restricted Access Page
|
|
152
|
+
// - /docs: Documentation
|
|
153
|
+
// - /manifest.webmanifest: Manifest
|
|
154
|
+
// - /sw.js: Service Worker
|
|
155
|
+
const isAllowedPath =
|
|
156
|
+
path === '/' ||
|
|
157
|
+
path === '/agents' ||
|
|
158
|
+
path.startsWith('/api/agents') ||
|
|
159
|
+
path.startsWith('/api/federated-agents') ||
|
|
160
|
+
path.startsWith('/api/auth') ||
|
|
161
|
+
path === '/restricted' ||
|
|
162
|
+
path.startsWith('/docs') ||
|
|
163
|
+
path === '/manifest.webmanifest' ||
|
|
164
|
+
path === '/sw.js';
|
|
165
|
+
|
|
166
|
+
if (isAllowedPath) {
|
|
167
|
+
return NextResponse.next();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Block access to other paths (e.g. Chat)
|
|
171
|
+
if (req.headers.get('accept')?.includes('text/html')) {
|
|
172
|
+
const url = req.nextUrl.clone();
|
|
173
|
+
url.pathname = '/restricted';
|
|
174
|
+
return NextResponse.rewrite(url);
|
|
175
|
+
}
|
|
176
|
+
return new NextResponse('Forbidden', { status: 403 });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// If we are here, the user is allowed (either by IP or session)
|
|
180
|
+
// Proceed with normal logic
|
|
181
|
+
|
|
182
|
+
// 3. Redirect /:agentName/* to /agents/:agentName/*
|
|
183
|
+
// This enables accessing agents from the root path
|
|
184
|
+
const pathParts = req.nextUrl.pathname.split('/');
|
|
185
|
+
const potentialAgentName = pathParts[1];
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
potentialAgentName &&
|
|
189
|
+
![
|
|
190
|
+
'agents',
|
|
191
|
+
'api',
|
|
192
|
+
'admin',
|
|
193
|
+
'docs',
|
|
194
|
+
'manifest.webmanifest',
|
|
195
|
+
'sw.js',
|
|
196
|
+
'test',
|
|
197
|
+
'embed',
|
|
198
|
+
'_next',
|
|
199
|
+
'favicon.ico',
|
|
200
|
+
].includes(potentialAgentName) &&
|
|
201
|
+
!potentialAgentName.startsWith('.') &&
|
|
202
|
+
// Note: Other static files are excluded by the matcher configuration below
|
|
203
|
+
true
|
|
204
|
+
) {
|
|
205
|
+
const url = req.nextUrl.clone();
|
|
206
|
+
url.pathname = `/agents${req.nextUrl.pathname}`;
|
|
207
|
+
const response = NextResponse.redirect(url);
|
|
208
|
+
|
|
209
|
+
// Enable CORS for the redirect
|
|
210
|
+
response.headers.set('Access-Control-Allow-Origin', '*');
|
|
211
|
+
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
212
|
+
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
|
213
|
+
|
|
214
|
+
return response;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 4. Custom Domain Routing
|
|
218
|
+
// If the host is not one of the configured SERVERS, try to find an agent with a matching META LINK
|
|
219
|
+
|
|
220
|
+
if (host && SERVERS && !SERVERS.some((server) => server === host)) {
|
|
221
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
222
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
223
|
+
|
|
224
|
+
if (supabaseUrl && supabaseKey) {
|
|
225
|
+
const supabase = createClient(supabaseUrl, supabaseKey, {
|
|
226
|
+
auth: {
|
|
227
|
+
persistSession: false,
|
|
228
|
+
autoRefreshToken: false,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Determine prefixes to check
|
|
233
|
+
// We check all configured servers because the custom domain could point to any of them
|
|
234
|
+
// (or if they share the database, we need to check the relevant tables)
|
|
235
|
+
const serversToCheck = SERVERS;
|
|
236
|
+
|
|
237
|
+
// TODO: [🧠] If there are many servers, this loop might be slow. Optimize if needed.
|
|
238
|
+
for (const serverHost of serversToCheck) {
|
|
239
|
+
let serverName = serverHost;
|
|
240
|
+
serverName = serverName.replace(/\.ptbk\.io$/, '');
|
|
241
|
+
serverName = normalizeTo_PascalCase(serverName);
|
|
242
|
+
const prefix = `server_${serverName}_`;
|
|
243
|
+
|
|
244
|
+
// Search for agent with matching META LINK
|
|
245
|
+
// agentProfile->links is an array of strings
|
|
246
|
+
// We check if it contains the host, or https://host, or http://host
|
|
247
|
+
|
|
248
|
+
const searchLinks = [host, `https://${host}`, `http://${host}`];
|
|
249
|
+
|
|
250
|
+
// Construct OR filter: agentProfile.cs.{"links":["link1"]},agentProfile.cs.{"links":["link2"]},...
|
|
251
|
+
const orFilter = searchLinks.map((link) => `agentProfile.cs.{"links":["${link}"]}`).join(',');
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const { data } = await supabase
|
|
255
|
+
.from(`${prefix}Agent`)
|
|
256
|
+
.select('agentName')
|
|
257
|
+
.or(orFilter)
|
|
258
|
+
.limit(1)
|
|
259
|
+
.single();
|
|
260
|
+
|
|
261
|
+
if (data && data.agentName) {
|
|
262
|
+
// Found the agent!
|
|
263
|
+
const url = req.nextUrl.clone();
|
|
264
|
+
url.pathname = `/${data.agentName}`;
|
|
265
|
+
|
|
266
|
+
// Pass the server context to the app via header
|
|
267
|
+
const requestHeaders = new Headers(req.headers);
|
|
268
|
+
requestHeaders.set('x-promptbook-server', serverHost);
|
|
269
|
+
|
|
270
|
+
return NextResponse.rewrite(url, {
|
|
271
|
+
request: {
|
|
272
|
+
headers: requestHeaders,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
178
275
|
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Ignore error (e.g. table not found, or agent not found) and continue to next server
|
|
278
|
+
// console.error(`Error checking server ${serverHost} for custom domain ${host}:`, error);
|
|
179
279
|
}
|
|
180
280
|
}
|
|
181
281
|
}
|
|
182
|
-
|
|
183
|
-
return NextResponse.next();
|
|
184
282
|
}
|
|
185
283
|
|
|
284
|
+
return NextResponse.next();
|
|
285
|
+
|
|
286
|
+
// This part should be unreachable due to logic above, but keeping as fallback
|
|
186
287
|
return new NextResponse('Forbidden', { status: 403 });
|
|
187
288
|
}
|
|
188
289
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VercelBlobStorage } from '../utils/cdn/classes/VercelBlobStorage';
|
|
2
2
|
import { IIFilesStorageWithCdn } from '../utils/cdn/interfaces/IFilesStorage';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -13,14 +13,10 @@ let cdn: IIFilesStorageWithCdn | null = null;
|
|
|
13
13
|
*/
|
|
14
14
|
export function $provideCdnForServer(): IIFilesStorageWithCdn {
|
|
15
15
|
if (!cdn) {
|
|
16
|
-
cdn = new
|
|
17
|
-
|
|
16
|
+
cdn = new VercelBlobStorage({
|
|
17
|
+
token: process.env.BLOB_READ_WRITE_TOKEN!,
|
|
18
18
|
pathPrefix: process.env.NEXT_PUBLIC_CDN_PATH_PREFIX!,
|
|
19
|
-
endpoint: process.env.CDN_ENDPOINT!,
|
|
20
|
-
accessKeyId: process.env.CDN_ACCESS_KEY_ID!,
|
|
21
|
-
secretAccessKey: process.env.CDN_SECRET_ACCESS_KEY!,
|
|
22
19
|
cdnPublicUrl: new URL(process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!),
|
|
23
|
-
gzip: true,
|
|
24
20
|
});
|
|
25
21
|
}
|
|
26
22
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
cacheLlmTools,
|
|
4
5
|
_AnthropicClaudeMetadataRegistration,
|
|
5
6
|
_AzureOpenAiMetadataRegistration,
|
|
6
7
|
_BoilerplateScraperMetadataRegistration,
|
|
@@ -26,6 +27,7 @@ import { $provideFilesystemForNode } from '../../../../src/scrapers/_common/regi
|
|
|
26
27
|
import { $provideScrapersForNode } from '../../../../src/scrapers/_common/register/$provideScrapersForNode';
|
|
27
28
|
import { $provideScriptingForNode } from '../../../../src/scrapers/_common/register/$provideScriptingForNode';
|
|
28
29
|
import { $sideEffect } from '../../../../src/utils/organization/$sideEffect';
|
|
30
|
+
import { SupabaseCacheStorage } from '../utils/cache/SupabaseCacheStorage';
|
|
29
31
|
|
|
30
32
|
$sideEffect(
|
|
31
33
|
_AnthropicClaudeMetadataRegistration,
|
|
@@ -90,10 +92,17 @@ export async function $provideExecutionToolsForServer(): Promise<ExecutionTools>
|
|
|
90
92
|
isCacheReloaded,
|
|
91
93
|
}; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
|
|
92
94
|
const fs = await $provideFilesystemForNode(prepareAndScrapeOptions);
|
|
93
|
-
const { /* [0] strategy,*/ llm } = await $provideLlmToolsForCli({
|
|
95
|
+
const { /* [0] strategy,*/ llm: llmUncached } = await $provideLlmToolsForCli({
|
|
94
96
|
cliOptions,
|
|
95
97
|
...prepareAndScrapeOptions,
|
|
96
98
|
});
|
|
99
|
+
|
|
100
|
+
const llm = cacheLlmTools(llmUncached, {
|
|
101
|
+
storage: new SupabaseCacheStorage(),
|
|
102
|
+
isVerbose,
|
|
103
|
+
isCacheReloaded,
|
|
104
|
+
});
|
|
105
|
+
|
|
97
106
|
const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
|
|
98
107
|
|
|
99
108
|
executionTools = {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NEXT_PUBLIC_SITE_URL, SERVERS, SUPABASE_TABLE_PREFIX } from '@/config';
|
|
2
2
|
import { normalizeTo_PascalCase } from '@promptbook-local/utils';
|
|
3
3
|
import { headers } from 'next/headers';
|
|
4
4
|
|
|
5
5
|
export async function $provideServer() {
|
|
6
6
|
if (!SERVERS) {
|
|
7
7
|
return {
|
|
8
|
-
publicUrl:
|
|
8
|
+
publicUrl: NEXT_PUBLIC_SITE_URL || new URL(`https://${(await headers()).get('host') || 'localhost:4440'}`),
|
|
9
9
|
tablePrefix: SUPABASE_TABLE_PREFIX,
|
|
10
10
|
};
|
|
11
11
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { $getTableName } from '../database/$getTableName';
|
|
2
|
+
import { $provideSupabaseForServer } from '../database/$provideSupabaseForServer';
|
|
3
|
+
import { AgentsServerDatabase } from '../database/schema';
|
|
4
|
+
import { verifyPassword } from './auth';
|
|
5
|
+
|
|
6
|
+
export type AuthenticatedUser = {
|
|
7
|
+
username: string;
|
|
8
|
+
isAdmin: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function authenticateUser(username: string, password: string): Promise<AuthenticatedUser | null> {
|
|
12
|
+
// 1. Check if it's the environment admin
|
|
13
|
+
if (username === 'admin' && process.env.ADMIN_PASSWORD && password === process.env.ADMIN_PASSWORD) {
|
|
14
|
+
return { username: 'admin', isAdmin: true };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 2. Check DB users
|
|
18
|
+
try {
|
|
19
|
+
const supabase = $provideSupabaseForServer();
|
|
20
|
+
const { data: user, error } = await supabase
|
|
21
|
+
.from(await $getTableName('User'))
|
|
22
|
+
.select('*')
|
|
23
|
+
.eq('username', username)
|
|
24
|
+
.single();
|
|
25
|
+
|
|
26
|
+
if (error || !user) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const userRow = user as AgentsServerDatabase['public']['Tables']['User']['Row'];
|
|
31
|
+
const isValid = await verifyPassword(password, userRow.passwordHash);
|
|
32
|
+
|
|
33
|
+
if (!isValid) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { username: userRow.username, isAdmin: userRow.isAdmin };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Authentication error:', error);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TODO_any } from '@promptbook-local/types';
|
|
2
|
+
import { $getTableName } from '../../database/$getTableName';
|
|
3
|
+
import { $provideSupabaseForServer } from '../../database/$provideSupabaseForServer';
|
|
4
|
+
import { Json } from '../../database/schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Storage for LLM cache using Supabase
|
|
8
|
+
*/
|
|
9
|
+
export class SupabaseCacheStorage {
|
|
10
|
+
// implements PromptbookStorage<TODO_any>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
|
|
14
|
+
*/
|
|
15
|
+
public async getItem(key: string): Promise<TODO_any | null> {
|
|
16
|
+
const supabase = $provideSupabaseForServer();
|
|
17
|
+
const tableName = await $getTableName('LlmCache');
|
|
18
|
+
|
|
19
|
+
const { data } = await supabase.from(tableName).select('value').eq('hash', key).maybeSingle();
|
|
20
|
+
|
|
21
|
+
if (!data) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return data.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
30
|
+
*/
|
|
31
|
+
public async setItem(key: string, value: TODO_any): Promise<void> {
|
|
32
|
+
const supabase = $provideSupabaseForServer();
|
|
33
|
+
const tableName = await $getTableName('LlmCache');
|
|
34
|
+
|
|
35
|
+
await supabase.from(tableName).upsert(
|
|
36
|
+
{
|
|
37
|
+
hash: key,
|
|
38
|
+
value: value as Json,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
onConflict: 'hash',
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists
|
|
48
|
+
*/
|
|
49
|
+
public async removeItem(key: string): Promise<void> {
|
|
50
|
+
const supabase = $provideSupabaseForServer();
|
|
51
|
+
const tableName = await $getTableName('LlmCache');
|
|
52
|
+
|
|
53
|
+
await supabase.from(tableName).delete().eq('hash', key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { del, put } from '@vercel/blob';
|
|
2
|
+
import { validateMimeType } from '../../validators/validateMimeType';
|
|
3
|
+
import type { IFile, IIFilesStorageWithCdn } from '../interfaces/IFilesStorage';
|
|
4
|
+
|
|
5
|
+
type IVercelBlobStorageConfig = {
|
|
6
|
+
readonly token: string;
|
|
7
|
+
readonly cdnPublicUrl: URL;
|
|
8
|
+
readonly pathPrefix?: string;
|
|
9
|
+
// Note: Vercel Blob automatically handles compression/serving
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class VercelBlobStorage implements IIFilesStorageWithCdn {
|
|
13
|
+
public get cdnPublicUrl() {
|
|
14
|
+
return this.config.cdnPublicUrl;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public constructor(private readonly config: IVercelBlobStorageConfig) {}
|
|
18
|
+
|
|
19
|
+
public getItemUrl(key: string): URL {
|
|
20
|
+
const path = this.config.pathPrefix ? `${this.config.pathPrefix}/${key}` : key;
|
|
21
|
+
return new URL(path, this.cdnPublicUrl);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async getItem(key: string): Promise<IFile | null> {
|
|
25
|
+
const url = this.getItemUrl(key);
|
|
26
|
+
|
|
27
|
+
const response = await fetch(url);
|
|
28
|
+
|
|
29
|
+
if (response.status === 404) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`Failed to fetch blob from ${url}: ${response.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
38
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
39
|
+
const contentType = response.headers.get('content-type') || 'application/octet-stream';
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
type: validateMimeType(contentType),
|
|
43
|
+
data: buffer,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async removeItem(key: string): Promise<void> {
|
|
48
|
+
const url = this.getItemUrl(key).toString();
|
|
49
|
+
await del(url, { token: this.config.token });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async setItem(key: string, file: IFile): Promise<void> {
|
|
53
|
+
const path = this.config.pathPrefix ? `${this.config.pathPrefix}/${key}` : key;
|
|
54
|
+
|
|
55
|
+
await put(path, file.data, {
|
|
56
|
+
access: 'public',
|
|
57
|
+
addRandomSuffix: false,
|
|
58
|
+
contentType: file.type,
|
|
59
|
+
token: this.config.token,
|
|
60
|
+
// Note: We rely on Vercel Blob for compression
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { AgentsServerDatabase } from '../database/schema';
|
|
2
|
+
|
|
3
|
+
export type ChatFeedbackRow = AgentsServerDatabase['public']['Tables']['ChatFeedback']['Row'];
|
|
4
|
+
export type ChatFeedbackSortField = 'createdAt' | 'agentName' | 'id';
|
|
5
|
+
export type ChatFeedbackSortOrder = 'asc' | 'desc';
|
|
6
|
+
|
|
7
|
+
export type ChatFeedbackListResponse = {
|
|
8
|
+
items: ChatFeedbackRow[];
|
|
9
|
+
total: number;
|
|
10
|
+
page: number;
|
|
11
|
+
pageSize: number;
|
|
12
|
+
sortBy: ChatFeedbackSortField;
|
|
13
|
+
sortOrder: ChatFeedbackSortOrder;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ChatFeedbackListParams = {
|
|
17
|
+
page?: number;
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
agentName?: string;
|
|
20
|
+
search?: string;
|
|
21
|
+
sortBy?: ChatFeedbackSortField;
|
|
22
|
+
sortOrder?: ChatFeedbackSortOrder;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build query string for chat feedback listing.
|
|
27
|
+
*
|
|
28
|
+
* Kept in a dedicated helper so it can be shared between the
|
|
29
|
+
* admin feedback page and other admin UIs (per-agent tools, etc.).
|
|
30
|
+
*/
|
|
31
|
+
function buildQuery(params: ChatFeedbackListParams): string {
|
|
32
|
+
const searchParams = new URLSearchParams();
|
|
33
|
+
|
|
34
|
+
if (params.page && params.page > 0) searchParams.set('page', String(params.page));
|
|
35
|
+
if (params.pageSize && params.pageSize > 0) searchParams.set('pageSize', String(params.pageSize));
|
|
36
|
+
if (params.agentName) searchParams.set('agentName', params.agentName);
|
|
37
|
+
if (params.search) searchParams.set('search', params.search);
|
|
38
|
+
if (params.sortBy) searchParams.set('sortBy', params.sortBy);
|
|
39
|
+
if (params.sortOrder) searchParams.set('sortOrder', params.sortOrder);
|
|
40
|
+
|
|
41
|
+
const qs = searchParams.toString();
|
|
42
|
+
return qs ? `?${qs}` : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fetch chat feedback from the admin API.
|
|
47
|
+
*/
|
|
48
|
+
export async function $fetchChatFeedback(params: ChatFeedbackListParams = {}): Promise<ChatFeedbackListResponse> {
|
|
49
|
+
const qs = buildQuery(params);
|
|
50
|
+
const response = await fetch(`/api/chat-feedback${qs}`, {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const data = await response.json().catch(() => ({}));
|
|
56
|
+
throw new Error(data.error || 'Failed to load chat feedback');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (await response.json()) as ChatFeedbackListResponse;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear chat feedback for a specific agent.
|
|
64
|
+
*/
|
|
65
|
+
export async function $clearAgentChatFeedback(agentName: string): Promise<void> {
|
|
66
|
+
if (!agentName) {
|
|
67
|
+
throw new Error('agentName is required to clear chat feedback');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const response = await fetch(`/api/chat-feedback?agentName=${encodeURIComponent(agentName)}`, {
|
|
71
|
+
method: 'DELETE',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const data = await response.json().catch(() => ({}));
|
|
76
|
+
throw new Error(data.error || 'Failed to clear chat feedback');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Delete a single chat feedback row by ID.
|
|
82
|
+
*/
|
|
83
|
+
export async function $deleteChatFeedbackRow(id: number): Promise<void> {
|
|
84
|
+
if (!id || id <= 0) {
|
|
85
|
+
throw new Error('Valid id is required to delete chat feedback row');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const response = await fetch(`/api/chat-feedback/${encodeURIComponent(String(id))}`, {
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const data = await response.json().catch(() => ({}));
|
|
94
|
+
throw new Error(data.error || 'Failed to delete chat feedback row');
|
|
95
|
+
}
|
|
96
|
+
}
|