@promptbook/cli 0.103.0-53 → 0.103.0-55

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 (76) hide show
  1. package/apps/agents-server/config.ts +0 -2
  2. package/apps/agents-server/src/app/admin/api-tokens/ApiTokensClient.tsx +186 -0
  3. package/apps/agents-server/src/app/admin/api-tokens/page.tsx +13 -0
  4. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +79 -6
  5. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +171 -69
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +10 -2
  7. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +203 -0
  8. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/route.ts +3 -1
  9. package/apps/agents-server/src/app/agents/[agentName]/api/modelRequirements/systemMessage/route.ts +3 -1
  10. package/apps/agents-server/src/app/agents/[agentName]/api/openai/chat/completions/route.ts +10 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/api/openrouter/chat/completions/route.ts +10 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +218 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +24 -3
  14. package/apps/agents-server/src/app/api/api-tokens/route.ts +76 -0
  15. package/apps/agents-server/src/app/api/auth/change-password/route.ts +75 -0
  16. package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +55 -0
  17. package/apps/agents-server/src/app/api/chat-history/export/route.ts +55 -0
  18. package/apps/agents-server/src/app/docs/[docId]/page.tsx +1 -0
  19. package/apps/agents-server/src/app/docs/page.tsx +1 -0
  20. package/apps/agents-server/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx +41 -0
  21. package/apps/agents-server/src/components/ChangePasswordForm/ChangePasswordForm.tsx +159 -0
  22. package/apps/agents-server/src/components/Header/Header.tsx +94 -33
  23. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +2 -1
  24. package/apps/agents-server/src/database/migrations/2025-12-0010-llm-cache.sql +12 -0
  25. package/apps/agents-server/src/database/migrations/2025-12-0060-api-tokens.sql +13 -0
  26. package/apps/agents-server/src/database/schema.ts +51 -0
  27. package/apps/agents-server/src/middleware.ts +50 -2
  28. package/apps/agents-server/src/tools/$provideCdnForServer.ts +3 -7
  29. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +10 -1
  30. package/apps/agents-server/src/utils/cache/SupabaseCacheStorage.ts +55 -0
  31. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +63 -0
  32. package/apps/agents-server/src/utils/convertToCsv.ts +31 -0
  33. package/apps/agents-server/src/utils/handleChatCompletion.ts +183 -0
  34. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +93 -0
  35. package/esm/index.es.js +846 -131
  36. package/esm/index.es.js.map +1 -1
  37. package/esm/typings/src/_packages/core.index.d.ts +8 -6
  38. package/esm/typings/src/_packages/types.index.d.ts +1 -1
  39. package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +4 -0
  40. package/esm/typings/src/commitments/ACTION/ACTION.d.ts +4 -0
  41. package/esm/typings/src/commitments/CLOSED/CLOSED.d.ts +35 -0
  42. package/esm/typings/src/commitments/COMPONENT/COMPONENT.d.ts +28 -0
  43. package/esm/typings/src/commitments/DELETE/DELETE.d.ts +4 -0
  44. package/esm/typings/src/commitments/FORMAT/FORMAT.d.ts +4 -0
  45. package/esm/typings/src/commitments/FROM/FROM.d.ts +34 -0
  46. package/esm/typings/src/commitments/GOAL/GOAL.d.ts +4 -0
  47. package/esm/typings/src/commitments/IMPORTANT/IMPORTANT.d.ts +26 -0
  48. package/esm/typings/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +4 -0
  49. package/esm/typings/src/commitments/LANGUAGE/LANGUAGE.d.ts +35 -0
  50. package/esm/typings/src/commitments/MEMORY/MEMORY.d.ts +4 -0
  51. package/esm/typings/src/commitments/MESSAGE/AgentMessageCommitmentDefinition.d.ts +4 -0
  52. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +4 -0
  53. package/esm/typings/src/commitments/MESSAGE/MESSAGE.d.ts +4 -0
  54. package/esm/typings/src/commitments/MESSAGE/UserMessageCommitmentDefinition.d.ts +4 -0
  55. package/esm/typings/src/commitments/META/META.d.ts +4 -0
  56. package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +4 -0
  57. package/esm/typings/src/commitments/META_IMAGE/META_IMAGE.d.ts +4 -0
  58. package/esm/typings/src/commitments/META_LINK/META_LINK.d.ts +4 -0
  59. package/esm/typings/src/commitments/MODEL/MODEL.d.ts +4 -0
  60. package/esm/typings/src/commitments/NOTE/NOTE.d.ts +4 -0
  61. package/esm/typings/src/commitments/OPEN/OPEN.d.ts +35 -0
  62. package/esm/typings/src/commitments/PERSONA/PERSONA.d.ts +4 -0
  63. package/esm/typings/src/commitments/RULE/RULE.d.ts +4 -0
  64. package/esm/typings/src/commitments/SAMPLE/SAMPLE.d.ts +4 -0
  65. package/esm/typings/src/commitments/SCENARIO/SCENARIO.d.ts +4 -0
  66. package/esm/typings/src/commitments/STYLE/STYLE.d.ts +4 -0
  67. package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +5 -0
  68. package/esm/typings/src/commitments/_base/CommitmentDefinition.d.ts +5 -0
  69. package/esm/typings/src/commitments/_base/NotYetImplementedCommitmentDefinition.d.ts +4 -0
  70. package/esm/typings/src/commitments/index.d.ts +1 -82
  71. package/esm/typings/src/commitments/registry.d.ts +68 -0
  72. package/esm/typings/src/version.d.ts +1 -1
  73. package/package.json +3 -3
  74. package/umd/index.umd.js +846 -131
  75. package/umd/index.umd.js.map +1 -1
  76. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +0 -119
@@ -0,0 +1,203 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
6
+ import { Agent } from '@promptbook-local/core';
7
+ import { ChatMessage, ChatPromptResult, Prompt, TODO_any } from '@promptbook-local/types';
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { z } from 'zod';
10
+
11
+ // Global map to store active transports
12
+ // Note: This works in stateful environments or single-instance deployments.
13
+ // In serverless with multiple instances, this will fail if POST lands on a different instance.
14
+ // However, for standard deployments or sticky sessions, it works.
15
+ const sessions = new Map<string, SSENextJsTransport>();
16
+
17
+ class SSENextJsTransport implements Transport {
18
+ public onmessage?: (message: JSONRPCMessage) => void;
19
+ public onclose?: () => void;
20
+ public onError?: (error: Error) => void;
21
+ private controller: ReadableStreamDefaultController<TODO_any>;
22
+ private encoder = new TextEncoder();
23
+ public sessionId: string;
24
+
25
+ constructor(controller: ReadableStreamDefaultController<TODO_any>, sessionId: string) {
26
+ this.controller = controller;
27
+ this.sessionId = sessionId;
28
+ }
29
+
30
+ async start(): Promise<void> {
31
+ // No-op for SSE
32
+ }
33
+
34
+ async close(): Promise<void> {
35
+ this.onclose?.();
36
+ sessions.delete(this.sessionId);
37
+ }
38
+
39
+ async send(message: JSONRPCMessage): Promise<void> {
40
+ const event = `event: message\ndata: ${JSON.stringify(message)}\n\n`;
41
+ this.controller.enqueue(this.encoder.encode(event));
42
+ }
43
+
44
+ async handlePostMessage(message: JSONRPCMessage): Promise<void> {
45
+ this.onmessage?.(message);
46
+ }
47
+
48
+ sendEndpointEvent(endpoint: string) {
49
+ const event = `event: endpoint\ndata: ${endpoint}\n\n`;
50
+ this.controller.enqueue(this.encoder.encode(event));
51
+ }
52
+ }
53
+
54
+ export async function GET(
55
+ request: NextRequest,
56
+ { params }: { params: Promise<{ agentName: string }> },
57
+ ) {
58
+ const { agentName } = await params;
59
+
60
+ // Check if agent exists
61
+ try {
62
+ const collection = await $provideAgentCollectionForServer();
63
+ await collection.getAgentSource(agentName);
64
+ } catch (error) {
65
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 });
66
+ }
67
+
68
+ const sessionId = crypto.randomUUID();
69
+
70
+ const stream = new ReadableStream({
71
+ start: async (controller) => {
72
+ const transport = new SSENextJsTransport(controller, sessionId);
73
+ sessions.set(sessionId, transport);
74
+
75
+ // Send endpoint event
76
+ // Construct the POST endpoint URL
77
+ // We assume the client can construct it or we send relative/absolute path
78
+ // The user wants: /agents/[agentName]/api/mcp
79
+ // We can send specific query param
80
+ const endpoint = `/agents/${agentName}/api/mcp?sessionId=${sessionId}`;
81
+ transport.sendEndpointEvent(endpoint);
82
+
83
+ // Initialize MCP Server
84
+ const server = new McpServer({
85
+ name: `Agent ${agentName}`,
86
+ version: '1.0.0',
87
+ });
88
+
89
+ // Register Chat Tool
90
+ server.tool(
91
+ 'chat',
92
+ {
93
+ messages: z.array(
94
+ z.object({
95
+ role: z.enum(['user', 'assistant', 'system']),
96
+ content: z.string(),
97
+ }),
98
+ ),
99
+ model: z.string().optional(),
100
+ },
101
+ async ({ messages, model }) => {
102
+ try {
103
+ const collection = await $provideAgentCollectionForServer();
104
+ const agentSource = await collection.getAgentSource(agentName);
105
+
106
+ const executionTools = await $provideExecutionToolsForServer();
107
+ const agent = new Agent({
108
+ agentSource,
109
+ executionTools,
110
+ isVerbose: true,
111
+ });
112
+
113
+ // Prepare thread and content
114
+ const lastMessage = messages[messages.length - 1];
115
+ const previousMessages = messages.slice(0, -1);
116
+
117
+ const thread: ChatMessage[] = previousMessages.map((msg: TODO_any, index: number) => ({
118
+ id: `msg-${index}`,
119
+ from: msg.role === 'assistant' ? 'agent' : 'user', // Mapping standard roles
120
+ content: msg.content,
121
+ isComplete: true,
122
+ date: new Date(),
123
+ }));
124
+
125
+ const prompt: Prompt = {
126
+ title: 'MCP Chat Completion',
127
+ content: lastMessage.content,
128
+ modelRequirements: {
129
+ modelVariant: 'CHAT',
130
+ },
131
+ parameters: {},
132
+ thread,
133
+ } as Prompt;
134
+
135
+ const result = await agent.callChatModel(prompt);
136
+
137
+ return {
138
+ content: [
139
+ {
140
+ type: 'text',
141
+ text: result.content,
142
+ },
143
+ ],
144
+ };
145
+ } catch (error) {
146
+ return {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: `Error: ${(error as Error).message}`,
151
+ },
152
+ ],
153
+ isError: true,
154
+ };
155
+ }
156
+ },
157
+ );
158
+
159
+ await server.connect(transport);
160
+
161
+ // Handle connection close
162
+ // In ReadableStream, verify if there is a way to detect close from client side in Next.js?
163
+ // Usually if client disconnects, the stream might be cancelled.
164
+ // But we don't have a direct hook here unless we return logic in 'cancel'.
165
+ },
166
+ cancel() {
167
+ sessions.delete(sessionId);
168
+ },
169
+ });
170
+
171
+ return new Response(stream, {
172
+ headers: {
173
+ 'Content-Type': 'text/event-stream',
174
+ 'Cache-Control': 'no-cache',
175
+ Connection: 'keep-alive',
176
+ },
177
+ });
178
+ }
179
+
180
+ export async function POST(
181
+ request: NextRequest,
182
+ { params }: { params: Promise<{ agentName: string }> },
183
+ ) {
184
+ const { searchParams } = new URL(request.url);
185
+ const sessionId = searchParams.get('sessionId');
186
+
187
+ if (!sessionId) {
188
+ return NextResponse.json({ error: 'Session ID required' }, { status: 400 });
189
+ }
190
+
191
+ const transport = sessions.get(sessionId);
192
+ if (!transport) {
193
+ return NextResponse.json({ error: 'Session not found' }, { status: 404 });
194
+ }
195
+
196
+ try {
197
+ const body = await request.json();
198
+ await transport.handlePostMessage(body);
199
+ return NextResponse.json({ success: true }); // Accepted
200
+ } catch (error) {
201
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
202
+ }
203
+ }
@@ -1,4 +1,5 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { resolveInheritedAgentSource } from '@/src/utils/resolveInheritedAgentSource';
2
3
  import { createAgentModelRequirements } from '@promptbook-local/core';
3
4
  import { serializeError } from '@promptbook-local/utils';
4
5
  import { assertsError } from '../../../../../../../../src/errors/assertsError';
@@ -12,7 +13,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
12
13
  try {
13
14
  const collection = await $provideAgentCollectionForServer();
14
15
  const agentSource = await collection.getAgentSource(agentName);
15
- const modelRequirements = await createAgentModelRequirements(agentSource);
16
+ const effectiveAgentSource = await resolveInheritedAgentSource(agentSource, collection);
17
+ const modelRequirements = await createAgentModelRequirements(effectiveAgentSource);
16
18
 
17
19
  return new Response(
18
20
  JSON.stringify(
@@ -1,4 +1,5 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { resolveInheritedAgentSource } from '@/src/utils/resolveInheritedAgentSource';
2
3
  import { createAgentModelRequirements } from '@promptbook-local/core';
3
4
  import { serializeError } from '@promptbook-local/utils';
4
5
  import { assertsError } from '../../../../../../../../../src/errors/assertsError';
@@ -12,7 +13,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
12
13
  try {
13
14
  const collection = await $provideAgentCollectionForServer();
14
15
  const agentSource = await collection.getAgentSource(agentName);
15
- const modelRequirements = await createAgentModelRequirements(agentSource);
16
+ const effectiveAgentSource = await resolveInheritedAgentSource(agentSource, collection);
17
+ const modelRequirements = await createAgentModelRequirements(effectiveAgentSource);
16
18
  const { systemMessage } = modelRequirements;
17
19
 
18
20
  return new Response(systemMessage, {
@@ -0,0 +1,10 @@
1
+ import { handleChatCompletion } from '@/src/utils/handleChatCompletion';
2
+ import { NextRequest } from 'next/server';
3
+
4
+ export async function POST(
5
+ request: NextRequest,
6
+ { params }: { params: Promise<{ agentName: string }> },
7
+ ) {
8
+ const { agentName } = await params;
9
+ return handleChatCompletion(request, { agentName }, 'OpenAI API Chat Completion');
10
+ }
@@ -0,0 +1,10 @@
1
+ import { handleChatCompletion } from '@/src/utils/handleChatCompletion';
2
+ import { NextRequest } from 'next/server';
3
+
4
+ export async function POST(
5
+ request: NextRequest,
6
+ { params }: { params: Promise<{ agentName: string }> },
7
+ ) {
8
+ const { agentName } = await params;
9
+ return handleChatCompletion(request, { agentName }, 'OpenRouter API Chat Completion');
10
+ }
@@ -0,0 +1,218 @@
1
+ 'use server';
2
+
3
+ import { $provideServer } from '@/src/tools/$provideServer';
4
+ import { Color } from '../../../../../../../src/utils/color/Color';
5
+ import { withAlpha } from '../../../../../../../src/utils/color/operators/withAlpha';
6
+ import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
7
+ import { ArrowLeftIcon, BookOpenIcon, CodeIcon, HistoryIcon, HomeIcon, LinkIcon, MessageSquareIcon, ShareIcon } from 'lucide-react';
8
+ import { headers } from 'next/headers';
9
+ import Link from 'next/link';
10
+ import { notFound } from 'next/navigation';
11
+ import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
12
+ import { CopyField } from '../CopyField';
13
+ import { getAgentName, getAgentProfile } from '../_utils';
14
+ import { generateAgentMetadata } from '../generateAgentMetadata';
15
+
16
+ export const generateMetadata = generateAgentMetadata;
17
+
18
+ export default async function AgentLinksPage({
19
+ params,
20
+ }: {
21
+ params: Promise<{ agentName: string }>;
22
+ }) {
23
+ $sideEffect(headers());
24
+ const agentName = await getAgentName(params);
25
+
26
+ let agentProfile;
27
+ try {
28
+ agentProfile = await getAgentProfile(agentName);
29
+ } catch (error) {
30
+ if (
31
+ error instanceof Error &&
32
+ (error.message.includes('Cannot coerce the result to a single JSON object') ||
33
+ error.message.includes('JSON object requested, multiple (or no) results returned'))
34
+ ) {
35
+ notFound();
36
+ }
37
+ throw error;
38
+ }
39
+
40
+ const { publicUrl } = await $provideServer();
41
+ const baseUrl = `${publicUrl.href}agents/${encodeURIComponent(agentName)}`;
42
+
43
+ // Extract brand color from meta
44
+ const brandColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
45
+ const backgroundColor = (await brandColor.then(withAlpha(0.05))).toHex();
46
+ const borderColor = (await brandColor.then(withAlpha(0.1))).toHex();
47
+ const primaryColor = (await brandColor).toHex();
48
+
49
+ return (
50
+ <div
51
+ className="min-h-screen p-6 md:p-12 flex flex-col items-center"
52
+ style={{
53
+ backgroundColor,
54
+ }}
55
+ >
56
+ <div className="w-full max-w-3xl bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
57
+
58
+ {/* Header */}
59
+ <div
60
+ className="p-6 border-b flex items-center gap-4"
61
+ style={{ borderColor }}
62
+ >
63
+ {agentProfile.meta.image && (
64
+ // eslint-disable-next-line @next/next/no-img-element
65
+ <img
66
+ src={agentProfile.meta.image as string}
67
+ alt={agentProfile.meta.fullname || agentName}
68
+ className="w-16 h-16 rounded-full object-cover border-2"
69
+ style={{ borderColor: primaryColor }}
70
+ />
71
+ )}
72
+ <div className="flex-1">
73
+ <h1 className="text-2xl font-bold text-gray-900">
74
+ {agentProfile.meta.fullname || agentName}
75
+ </h1>
76
+ <p className="text-gray-500 flex items-center gap-2">
77
+ <LinkIcon className="w-4 h-4" />
78
+ Signpost & Links
79
+ </p>
80
+ </div>
81
+ <Link
82
+ href={`/agents/${encodeURIComponent(agentName)}`}
83
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors"
84
+ title="Back to Agent"
85
+ >
86
+ <ArrowLeftIcon className="w-6 h-6" />
87
+ </Link>
88
+ </div>
89
+
90
+ <div className="divide-y divide-gray-100">
91
+
92
+ {/* API Endpoints */}
93
+ <div className="p-6">
94
+ <h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
95
+ <CodeIcon className="w-5 h-5 text-gray-500" />
96
+ API Endpoints
97
+ </h2>
98
+ <div className="grid gap-4">
99
+ <div>
100
+ <h3 className="text-sm font-medium text-gray-700 mb-1">OpenAI Compatible Chat Completion</h3>
101
+ <p className="text-xs text-gray-500 mb-2">Standard OpenAI API endpoint for chat completions.</p>
102
+ <CopyField label="Endpoint URL" value={`${baseUrl}/api/openai/chat/completions`} />
103
+ </div>
104
+
105
+ <div>
106
+ <h3 className="text-sm font-medium text-gray-700 mb-1">OpenRouter Compatible</h3>
107
+ <p className="text-xs text-gray-500 mb-2">Endpoint compatible with OpenRouter API format.</p>
108
+ <CopyField label="Endpoint URL" value={`${baseUrl}/api/openrouter/chat/completions`} />
109
+ </div>
110
+
111
+ <div>
112
+ <h3 className="text-sm font-medium text-gray-700 mb-1">Model Context Protocol (MCP)</h3>
113
+ <p className="text-xs text-gray-500 mb-2">Endpoint for Model Context Protocol integration.</p>
114
+ <CopyField label="Endpoint URL" value={`${baseUrl}/api/mcp`} />
115
+ </div>
116
+
117
+ <div>
118
+ <h3 className="text-sm font-medium text-gray-700 mb-1">Model Requirements</h3>
119
+ <p className="text-xs text-gray-500 mb-2">Get requirements and capabilities of the model.</p>
120
+ <CopyField label="Endpoint URL" value={`${baseUrl}/api/modelRequirements`} />
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ {/* Agent Resources */}
126
+ <div className="p-6">
127
+ <h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
128
+ <HomeIcon className="w-5 h-5 text-gray-500" />
129
+ Agent Resources
130
+ </h2>
131
+ <div className="grid md:grid-cols-2 gap-4">
132
+ <Link
133
+ href={`/agents/${encodeURIComponent(agentName)}`}
134
+ className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
135
+ >
136
+ <div className="flex items-center gap-3 mb-2">
137
+ <div className="p-2 rounded-lg bg-blue-50 text-blue-600 group-hover:bg-blue-100 transition-colors">
138
+ <MessageSquareIcon className="w-5 h-5" />
139
+ </div>
140
+ <span className="font-medium text-gray-900">Chat with Agent</span>
141
+ </div>
142
+ <p className="text-sm text-gray-500">
143
+ Direct interface to converse with {agentProfile.meta.fullname || agentName}.
144
+ </p>
145
+ </Link>
146
+
147
+ <Link
148
+ href={`/agents/${encodeURIComponent(agentName)}/history`}
149
+ className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
150
+ >
151
+ <div className="flex items-center gap-3 mb-2">
152
+ <div className="p-2 rounded-lg bg-purple-50 text-purple-600 group-hover:bg-purple-100 transition-colors">
153
+ <HistoryIcon className="w-5 h-5" />
154
+ </div>
155
+ <span className="font-medium text-gray-900">History & Feedback</span>
156
+ </div>
157
+ <p className="text-sm text-gray-500">
158
+ View past conversations and provide feedback.
159
+ </p>
160
+ </Link>
161
+
162
+ <Link
163
+ href={`/agents/${encodeURIComponent(agentName)}/integration`}
164
+ className="block p-4 rounded-lg border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all group bg-white"
165
+ >
166
+ <div className="flex items-center gap-3 mb-2">
167
+ <div className="p-2 rounded-lg bg-green-50 text-green-600 group-hover:bg-green-100 transition-colors">
168
+ <CodeIcon className="w-5 h-5" />
169
+ </div>
170
+ <span className="font-medium text-gray-900">Integration Guide</span>
171
+ </div>
172
+ <p className="text-sm text-gray-500">
173
+ Learn how to integrate this agent into your applications.
174
+ </p>
175
+ </Link>
176
+ </div>
177
+ </div>
178
+
179
+ {/* Ecosystem */}
180
+ <div className="p-6 bg-gray-50">
181
+ <h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
182
+ <ShareIcon className="w-5 h-5 text-gray-500" />
183
+ Promptbook Ecosystem
184
+ </h2>
185
+ <div className="grid md:grid-cols-2 gap-4">
186
+ <a
187
+ href="https://promptbook.studio"
188
+ target="_blank"
189
+ rel="noopener noreferrer"
190
+ className="flex items-center gap-3 p-3 rounded-lg bg-white border border-gray-200 hover:border-gray-300 transition-colors"
191
+ >
192
+ <BookOpenIcon className="w-5 h-5 text-gray-400" />
193
+ <div>
194
+ <div className="font-medium text-gray-900">Promptbook Studio</div>
195
+ <div className="text-xs text-gray-500">Create and manage your own agents</div>
196
+ </div>
197
+ </a>
198
+
199
+ <a
200
+ href="https://github.com/webgptorg/promptbook"
201
+ target="_blank"
202
+ rel="noopener noreferrer"
203
+ className="flex items-center gap-3 p-3 rounded-lg bg-white border border-gray-200 hover:border-gray-300 transition-colors"
204
+ >
205
+ <CodeIcon className="w-5 h-5 text-gray-400" />
206
+ <div>
207
+ <div className="font-medium text-gray-900">GitHub Repository</div>
208
+ <div className="text-xs text-gray-500">Star us and contribute to the project</div>
209
+ </div>
210
+ </a>
211
+ </div>
212
+ </div>
213
+
214
+ </div>
215
+ </div>
216
+ </div>
217
+ );
218
+ }
@@ -3,7 +3,7 @@
3
3
  // import { BookEditor } from '@promptbook-local/components';
4
4
  import { $provideServer } from '@/src/tools/$provideServer';
5
5
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
6
- import { CodeIcon, HistoryIcon, NotebookPenIcon } from 'lucide-react';
6
+ import { CodeIcon, HistoryIcon, MessageCircleQuestionIcon, MessageSquareIcon, NotebookPenIcon } from 'lucide-react';
7
7
  import { headers } from 'next/headers';
8
8
  import { notFound } from 'next/navigation';
9
9
  import { Color } from '../../../../../../src/utils/color/Color';
@@ -26,13 +26,20 @@ import { isUserAdmin } from '../../../utils/isUserAdmin';
26
26
 
27
27
  export const generateMetadata = generateAgentMetadata;
28
28
 
29
- export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
29
+ export default async function AgentPage({
30
+ params,
31
+ searchParams,
32
+ }: {
33
+ params: Promise<{ agentName: string }>;
34
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
35
+ }) {
30
36
  // const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
31
37
  // const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
32
38
  // const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
33
39
 
34
40
  $sideEffect(headers());
35
41
 
42
+ const { message } = await searchParams;
36
43
  const agentName = await getAgentName(params);
37
44
  const isAdmin = await isUserAdmin();
38
45
 
@@ -155,6 +162,20 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
155
162
  Maintenance
156
163
  </h2>
157
164
  <div className="flex flex-col gap-2">
165
+ <a
166
+ href={`/admin/chat-history?agentName=${encodeURIComponent(agentName)}`}
167
+ className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
168
+ >
169
+ <MessageSquareIcon className="mr-2 w-3 h-3" />
170
+ Chat history
171
+ </a>
172
+ <a
173
+ href={`/admin/chat-feedback?agentName=${encodeURIComponent(agentName)}`}
174
+ className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-gray-300 bg-white px-3 py-1.5 text-xs font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
175
+ >
176
+ <MessageCircleQuestionIcon className="mr-2 w-3 h-3" />
177
+ Chat feedback
178
+ </a>
158
179
  <ClearAgentChatHistoryButton agentName={agentName} />
159
180
  <ClearAgentChatFeedbackButton agentName={agentName} />
160
181
  </div>
@@ -181,7 +202,7 @@ export default async function AgentPage({ params }: { params: Promise<{ agentNam
181
202
 
182
203
  {/* Main content: Chat */}
183
204
  <div className="flex-1 relative h-full bg-white">
184
- <AgentChatWrapper agentUrl={agentUrl} />
205
+ <AgentChatWrapper agentUrl={agentUrl} defaultMessage={message as string} />
185
206
  </div>
186
207
  </div>
187
208
  );
@@ -0,0 +1,76 @@
1
+ import { $getTableName } from '../../../database/$getTableName';
2
+ import { $provideSupabase } from '../../../database/$provideSupabase';
3
+ import { isUserAdmin } from '../../../utils/isUserAdmin';
4
+ import { randomUUID } from 'crypto';
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+
7
+ export async function GET(request: NextRequest) {
8
+ if (!(await isUserAdmin())) {
9
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
10
+ }
11
+
12
+ const supabase = $provideSupabase();
13
+ const table = await $getTableName('ApiTokens');
14
+
15
+ const { data, error } = await supabase.from(table).select('*').order('createdAt', { ascending: false });
16
+
17
+ if (error) {
18
+ return NextResponse.json({ error: error.message }, { status: 500 });
19
+ }
20
+
21
+ return NextResponse.json(data);
22
+ }
23
+
24
+ export async function POST(request: NextRequest) {
25
+ if (!(await isUserAdmin())) {
26
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
27
+ }
28
+
29
+ try {
30
+ const body = await request.json();
31
+ const { note } = body;
32
+
33
+ const token = `ptbk_${randomUUID().replace(/-/g, '')}`;
34
+
35
+ const supabase = $provideSupabase();
36
+ const table = await $getTableName('ApiTokens');
37
+
38
+ const { data, error } = await supabase
39
+ .from(table)
40
+ .insert({ token, note })
41
+ .select()
42
+ .single();
43
+
44
+ if (error) {
45
+ return NextResponse.json({ error: error.message }, { status: 500 });
46
+ }
47
+
48
+ return NextResponse.json(data);
49
+ } catch (e) {
50
+ return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
51
+ }
52
+ }
53
+
54
+ export async function DELETE(request: NextRequest) {
55
+ if (!(await isUserAdmin())) {
56
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
57
+ }
58
+
59
+ const searchParams = request.nextUrl.searchParams;
60
+ const id = searchParams.get('id');
61
+
62
+ if (!id) {
63
+ return NextResponse.json({ error: 'ID is required' }, { status: 400 });
64
+ }
65
+
66
+ const supabase = $provideSupabase();
67
+ const table = await $getTableName('ApiTokens');
68
+
69
+ const { error } = await supabase.from(table).delete().eq('id', parseInt(id, 10));
70
+
71
+ if (error) {
72
+ return NextResponse.json({ error: error.message }, { status: 500 });
73
+ }
74
+
75
+ return NextResponse.json({ success: true });
76
+ }