@promptbook/cli 0.103.0-55 → 0.103.0-56

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 (61) hide show
  1. package/apps/agents-server/package-lock.json +1163 -0
  2. package/apps/agents-server/package.json +6 -0
  3. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +3 -1
  4. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +216 -0
  5. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +78 -0
  6. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileView.tsx +233 -0
  7. package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +4 -4
  8. package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +2 -2
  9. package/apps/agents-server/src/app/agents/[agentName]/QrCodeModal.tsx +90 -0
  10. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +80 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +3 -1
  12. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +11 -1
  13. package/apps/agents-server/src/app/agents/[agentName]/api/openai/models/route.ts +93 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/chat/completions/route.ts +10 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/api/openai/v1/models/route.ts +93 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +4 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +9 -2
  18. package/apps/agents-server/src/app/agents/[agentName]/integration/SdkCodeTabs.tsx +31 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +271 -30
  20. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +61 -97
  21. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +108 -165
  22. package/apps/agents-server/src/app/agents/[agentName]/website-integration/page.tsx +61 -0
  23. package/apps/agents-server/src/app/api/openai/v1/chat/completions/route.ts +6 -0
  24. package/apps/agents-server/src/app/api/openai/v1/models/route.ts +65 -0
  25. package/apps/agents-server/src/app/docs/[docId]/page.tsx +12 -32
  26. package/apps/agents-server/src/app/docs/page.tsx +42 -17
  27. package/apps/agents-server/src/app/globals.css +129 -0
  28. package/apps/agents-server/src/app/layout.tsx +8 -2
  29. package/apps/agents-server/src/app/manifest.ts +1 -1
  30. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +87 -0
  31. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +20 -0
  32. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +18 -0
  33. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +18 -0
  34. package/apps/agents-server/src/database/migrations/2025-12-0070-chat-history-source.sql +2 -0
  35. package/apps/agents-server/src/database/schema.ts +6 -0
  36. package/apps/agents-server/src/utils/handleChatCompletion.ts +186 -14
  37. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +13 -6
  38. package/apps/agents-server/src/utils/validateApiKey.ts +128 -0
  39. package/apps/agents-server/tailwind.config.ts +1 -1
  40. package/esm/index.es.js +865 -441
  41. package/esm/index.es.js.map +1 -1
  42. package/esm/typings/src/_packages/core.index.d.ts +6 -8
  43. package/esm/typings/src/_packages/types.index.d.ts +1 -1
  44. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +5 -0
  45. package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +6 -0
  46. package/esm/typings/src/commitments/META_FONT/META_FONT.d.ts +42 -0
  47. package/esm/typings/src/commitments/USE/USE.d.ts +53 -0
  48. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +38 -0
  49. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.test.d.ts +1 -0
  50. package/esm/typings/src/commitments/{IMPORTANT/IMPORTANT.d.ts → USE_MCP/USE_MCP.d.ts} +16 -5
  51. package/esm/typings/src/commitments/USE_SEARCH_ENGINE/USE_SEARCH_ENGINE.d.ts +38 -0
  52. package/esm/typings/src/commitments/index.d.ts +93 -1
  53. package/esm/typings/src/playground/playground.d.ts +3 -0
  54. package/esm/typings/src/utils/color/Color.d.ts +8 -0
  55. package/esm/typings/src/utils/color/css-colors.d.ts +1 -0
  56. package/esm/typings/src/version.d.ts +1 -1
  57. package/package.json +1 -1
  58. package/umd/index.umd.js +861 -437
  59. package/umd/index.umd.js.map +1 -1
  60. package/esm/typings/src/commitments/registry.d.ts +0 -68
  61. package/esm/typings/src/playground/playground1.d.ts +0 -2
@@ -1,45 +1,21 @@
1
1
  'use server';
2
2
 
3
- // import { BookEditor } from '@promptbook-local/components';
4
3
  import { $provideServer } from '@/src/tools/$provideServer';
4
+ import { isUserAdmin } from '@/src/utils/isUserAdmin';
5
5
  import { PROMPTBOOK_COLOR } from '@promptbook-local/core';
6
- import { CodeIcon, HistoryIcon, MessageCircleQuestionIcon, MessageSquareIcon, NotebookPenIcon } from 'lucide-react';
7
- import { headers } from 'next/headers';
8
6
  import { notFound } from 'next/navigation';
7
+ import spaceTrim from 'spacetrim';
9
8
  import { Color } from '../../../../../../src/utils/color/Color';
10
- import { withAlpha } from '../../../../../../src/utils/color/operators/withAlpha';
11
- import { $sideEffect } from '../../../../../../src/utils/organization/$sideEffect';
12
- import { AGENT_ACTIONS, getAgentName, getAgentProfile } from './_utils';
13
- import { AgentChatWrapper } from './AgentChatWrapper';
14
- import { AgentQrCode } from './AgentQrCode';
15
- import { CopyField } from './CopyField';
9
+ import { darken } from '../../../../../../src/utils/color/operators/darken';
10
+ import { lighten } from '../../../../../../src/utils/color/operators/lighten';
11
+ import { getAgentName, getAgentProfile } from './_utils';
12
+ import { AgentProfileView } from './AgentProfileView';
16
13
  import { generateAgentMetadata } from './generateAgentMetadata';
17
- import { InstallPwaButton } from './InstallPwaButton';
18
14
  import { ServiceWorkerRegister } from './ServiceWorkerRegister';
19
- import { ClearAgentChatHistoryButton } from './ClearAgentChatHistoryButton';
20
- import { ClearAgentChatFeedbackButton } from './ClearAgentChatFeedbackButton';
21
- import { CloneAgentButton } from './CloneAgentButton';
22
- import { isUserAdmin } from '../../../utils/isUserAdmin';
23
- // import { Agent } from '@promptbook-local/core';
24
- // import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
25
- // import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
26
15
 
27
16
  export const generateMetadata = generateAgentMetadata;
28
17
 
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
- }) {
36
- // const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
37
- // const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
38
- // const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
39
-
40
- $sideEffect(headers());
41
-
42
- const { message } = await searchParams;
18
+ export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
43
19
  const agentName = await getAgentName(params);
44
20
  const isAdmin = await isUserAdmin();
45
21
 
@@ -66,145 +42,112 @@ export default async function AgentPage({
66
42
 
67
43
  const agentEmail = `${agentName}@${publicUrl.hostname}`;
68
44
 
69
- console.log('[🐱‍🚀]', { pageUrl: agentUrl });
45
+ // Extract brand color from meta and create color variations
46
+ const brandColorString = agentProfile.meta.color || PROMPTBOOK_COLOR.toHex();
47
+ const brandColors = brandColorString.split(',').map((c) => Color.fromSafe(c.trim()));
70
48
 
71
- // Extract brand color from meta
72
- const brandColor = Color.from(agentProfile.meta.color || PROMPTBOOK_COLOR);
49
+ // Ensure at least one color
50
+ if (brandColors.length === 0) {
51
+ brandColors.push(PROMPTBOOK_COLOR);
52
+ }
73
53
 
74
- // Mock agent actions
75
- const agentActions = AGENT_ACTIONS;
54
+ const brandColor = brandColors[0]!;
55
+ const brandColorHex = brandColor.toHex();
56
+ const brandColorLightHex = brandColor.then(lighten(0.2)).toHex();
57
+ const brandColorDarkHex = brandColor.then(darken(0.15)).toHex();
58
+ const brandColorsHex = brandColors.map((c) => c.toHex());
59
+
60
+ // Generate Noisy SVG Background
61
+ const color1 = brandColors[0]!;
62
+ const color2 = brandColors[1] || brandColors[0]!; // Use secondary color or fallback to primary
63
+
64
+ // [🧠] Make colors much lighter for the background as per feedback
65
+ const color1Light = color1.then(lighten(0.3)).toHex();
66
+ const color1Main = color1.toHex();
67
+ const color1Dark = color1.then(darken(0.3)).toHex();
68
+
69
+ const color2Light = color2.then(lighten(0.3)).toHex();
70
+ const color2Main = color2.toHex();
71
+ const color2Dark = color2.then(darken(0.3)).toHex();
72
+
73
+ const svgContent = spaceTrim(`
74
+ <svg xmlns="http://www.w3.org/2000/svg"
75
+ viewBox="0 0 1920 1080"
76
+ width="1920" height="1080"
77
+ preserveAspectRatio="xMidYMid slice">
78
+ <defs>
79
+ <!-- Bottom-left -->
80
+ <radialGradient id="grad1" cx="0%" cy="100%" r="90%">
81
+ <stop offset="0%" stop-color="${color1Light}" />
82
+ <stop offset="50%" stop-color="${color1Main}" />
83
+ <stop offset="100%" stop-color="${color1Dark}" />
84
+ </radialGradient>
85
+
86
+ <!-- Bottom-right -->
87
+ <radialGradient id="grad2" cx="100%" cy="100%" r="90%">
88
+ <stop offset="0%" stop-color="${color2Light}" />
89
+ <stop offset="50%" stop-color="${color2Main}" />
90
+ <stop offset="100%" stop-color="${color2Dark}" />
91
+ </radialGradient>
92
+
93
+ <!-- White top fade -->
94
+ <linearGradient id="whiteTopGrad" x1="0%" y1="0%" x2="0%" y2="100%">
95
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="1" />
96
+ <stop offset="100%" stop-color="#ffffff" stop-opacity="0.3" />
97
+ </linearGradient>
98
+
99
+ <!-- Strong grain -->
100
+ <filter id="grain" x="-10%" y="-10%" width="120%" height="120%">
101
+ <feTurbulence type="fractalNoise" baseFrequency="3.5" numOctaves="3" seed="8" result="noise" />
102
+ <feComponentTransfer>
103
+ <feFuncR type="linear" slope="3.5" intercept="-1.2" />
104
+ <feFuncG type="linear" slope="3.5" intercept="-1.2" />
105
+ <feFuncB type="linear" slope="3.5" intercept="-1.2" />
106
+ <feFuncA type="table" tableValues="0 0.8" />
107
+ </feComponentTransfer>
108
+ </filter>
109
+ </defs>
110
+
111
+ <!-- White base -->
112
+ <rect width="100%" height="100%" fill="#ffffff" />
113
+
114
+ <!-- Gradients -->
115
+ <rect width="100%" height="100%" fill="url(#grad1)" />
116
+ <rect width="100%" height="100%" fill="url(#grad2)" style="mix-blend-mode:screen; opacity:0.85" />
117
+
118
+ <!-- White fade on top -->
119
+ <rect width="100%" height="100%" fill="url(#whiteTopGrad)" />
120
+
121
+ <!-- Strong visible noise -->
122
+ <rect width="100%" height="100%" filter="url(#grain)"
123
+ style="mix-blend-mode:soft-light; opacity:1.2" />
124
+ </svg>
125
+ `);
126
+
127
+ const backgroundImage = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
128
+
129
+ const fullname = (agentProfile.meta.fullname || agentProfile.agentName || 'Agent') as string;
130
+ const imageUrl = (agentProfile.meta.image as string) || null;
76
131
 
77
132
  return (
78
- <div className="flex flex-col md:flex-row h-[calc(100vh-60px)] w-full overflow-hidden">
133
+ <>
79
134
  <ServiceWorkerRegister scope={`/agents/${encodeURIComponent(agentName)}/`} />
80
-
81
- {/* Left sidebar: Profile info */}
82
- <div
83
- className="w-full md:w-[400px] flex flex-col gap-6 p-6 overflow-y-auto border-r bg-gray-50 flex-shrink-0"
84
- style={{
85
- backgroundColor: brandColor.then(withAlpha(0.05)).toHex(),
86
- borderColor: brandColor.then(withAlpha(0.1)).toHex(),
87
- }}
88
- >
89
- <div className="flex items-center gap-4">
90
- {agentProfile.meta.image && (
91
- // eslint-disable-next-line @next/next/no-img-element
92
- <img
93
- src={agentProfile.meta.image as string}
94
- alt={agentProfile.meta.fullname || agentProfile.agentName || 'Agent'}
95
- width={64}
96
- height={64}
97
- className="rounded-full object-cover border-2 aspect-square w-16 h-16"
98
- style={{ borderColor: brandColor.toHex() }}
99
- />
100
- )}
101
- <div className="flex-1">
102
- <h1 className="text-3xl font-bold text-gray-900 break-words">
103
- {agentProfile.meta.fullname || agentProfile.agentName}
104
- </h1>
105
- <span
106
- className="inline-block mt-1 px-2 py-1 rounded text-xs font-semibold text-white"
107
- style={{ backgroundColor: brandColor.toHex() }}
108
- >
109
- Agent
110
- </span>
111
- </div>
112
- </div>
113
-
114
- <p className="text-gray-700">{agentProfile.personaDescription}</p>
115
-
116
- <div className="flex flex-col gap-2">
117
- <h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Capabilities</h2>
118
- <div className="flex flex-wrap gap-2">
119
- {agentActions.map((action) => (
120
- <span
121
- key={action}
122
- className="px-3 py-1 bg-white text-gray-700 rounded-full text-xs font-medium border border-gray-200 shadow-sm"
123
- >
124
- {action}
125
- </span>
126
- ))}
127
- </div>
128
- </div>
129
-
130
- <div className="flex flex-col gap-3 mt-auto">
131
- <div className="flex gap-2">
132
- <a
133
- href={`/agents/${encodeURIComponent(agentName)}/book+chat`}
134
- // <- TODO: [🧠] Can I append path like this on current browser URL in href?
135
- className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
136
- >
137
- <NotebookPenIcon className="ml-2 w-4 h-4 mr-2" />
138
- Edit
139
- </a>
140
- <a
141
- href={`/agents/${encodeURIComponent(agentName)}/integration`}
142
- // <- TODO: [🧠] Can I append path like this on current browser URL in href?
143
- className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
144
- >
145
- <CodeIcon className="ml-2 w-4 h-4 mr-2" />
146
- Integration
147
- </a>
148
- <a
149
- href={`/agents/${encodeURIComponent(agentName)}/history`}
150
- className="flex-1 inline-flex items-center justify-center whitespace-nowrap bg-white hover:bg-gray-100 text-gray-800 px-4 py-2 rounded shadow font-semibold transition border border-gray-200"
151
- >
152
- <HistoryIcon className="ml-2 w-4 h-4 mr-2" />
153
- History
154
- </a>
155
- {isAdmin && <CloneAgentButton agentName={agentName} />}
156
- <InstallPwaButton />
157
- </div>
158
-
159
- {isAdmin && (
160
- <div className="border-t border-dashed border-gray-300 pt-3">
161
- <h2 className="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-600">
162
- Maintenance
163
- </h2>
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>
179
- <ClearAgentChatHistoryButton agentName={agentName} />
180
- <ClearAgentChatFeedbackButton agentName={agentName} />
181
- </div>
182
- </div>
183
- )}
184
- </div>
185
-
186
- <div className="flex flex-col items-center gap-4 pt-6 border-t border-gray-200 w-full">
187
- <div className="bg-white rounded-lg p-4 flex flex-col items-center shadow-sm border border-gray-100">
188
- <AgentQrCode
189
- agentName={agentProfile.agentName || 'Agent'}
190
- meta={agentProfile.meta}
191
- personaDescription={agentProfile.personaDescription}
192
- agentUrl={agentUrl}
193
- agentEmail={agentEmail}
194
- />
195
- </div>
196
- <div className="flex flex-col gap-2 w-full">
197
- <CopyField label="Agent Page URL" value={agentUrl} />
198
- <CopyField label="Agent Email" value={agentEmail} />
199
- </div>
200
- </div>
201
- </div>
202
-
203
- {/* Main content: Chat */}
204
- <div className="flex-1 relative h-full bg-white">
205
- <AgentChatWrapper agentUrl={agentUrl} defaultMessage={message as string} />
206
- </div>
207
- </div>
135
+ <AgentProfileView
136
+ agentName={agentName}
137
+ fullname={fullname}
138
+ personaDescription={agentProfile.personaDescription || ''}
139
+ imageUrl={imageUrl}
140
+ agentUrl={agentUrl}
141
+ agentEmail={agentEmail}
142
+ brandColorHex={brandColorHex}
143
+ brandColorLightHex={brandColorLightHex}
144
+ brandColorDarkHex={brandColorDarkHex}
145
+ brandColorsHex={brandColorsHex}
146
+ backgroundImage={backgroundImage}
147
+ meta={agentProfile.meta}
148
+ isAdmin={isAdmin}
149
+ />
150
+ </>
208
151
  );
209
152
  }
210
153
 
@@ -0,0 +1,61 @@
1
+ 'use server';
2
+
3
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { $provideServer } from '@/src/tools/$provideServer';
5
+ import { PromptbookAgent } from '@promptbook-local/components';
6
+ import { parseAgentSource } from '@promptbook-local/core';
7
+ import { headers } from 'next/headers';
8
+ import spaceTrim from 'spacetrim';
9
+ import { $sideEffect } from '../../../../../../../src/utils/organization/$sideEffect';
10
+ import { CodePreview } from '../../../../../../_common/components/CodePreview/CodePreview';
11
+ import { generateAgentMetadata } from '../generateAgentMetadata';
12
+
13
+ export const generateMetadata = generateAgentMetadata;
14
+
15
+ export default async function WebsiteIntegrationAgentPage({ params }: { params: Promise<{ agentName: string }> }) {
16
+ $sideEffect(headers());
17
+ let { agentName } = await params;
18
+ agentName = decodeURIComponent(agentName);
19
+
20
+ const collection = await $provideAgentCollectionForServer();
21
+ const agentSource = await collection.getAgentSource(agentName);
22
+ const { meta } = parseAgentSource(agentSource);
23
+ const { fullname, color, image, ...restMeta } = meta;
24
+ const { publicUrl } = await $provideServer();
25
+ const agentUrl = `${publicUrl.href}agents/${encodeURIComponent(agentName)}`;
26
+
27
+ const code = spaceTrim(
28
+ (block) => `
29
+
30
+ import { PromptbookAgent } from '@promptbook/components';
31
+
32
+ export function YourComponent() {
33
+ return(
34
+ <PromptbookAgent
35
+ agentUrl="${agentUrl}"
36
+ meta={${block(JSON.stringify({ fullname, color, image, ...restMeta }, null, 4))}}
37
+ />
38
+ );
39
+ }
40
+
41
+ `,
42
+ );
43
+
44
+ return (
45
+ <main className="w-screen h-screen p-4">
46
+ <h1 className="text-2xl font-bold p-4 border-b">{meta.fullname || agentName} Integration Code</h1>
47
+ <p className="mt-4 mb-8 text-gray-600">
48
+ Use the following code to integrate the <strong>{meta.fullname || agentName}</strong> agent into your
49
+ React application using the <code>{'<PromptbookAgent />'}</code> component.
50
+ </p>
51
+
52
+ <CodePreview code={code} />
53
+ <PromptbookAgent agentUrl={agentUrl} meta={meta} />
54
+ </main>
55
+ );
56
+ }
57
+
58
+ /**
59
+ * TODO: Make this page better, bring from Promptbook.Studio
60
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
61
+ */
@@ -0,0 +1,6 @@
1
+ import { handleChatCompletion } from '@/src/utils/handleChatCompletion';
2
+ import { NextRequest } from 'next/server';
3
+
4
+ export async function POST(request: NextRequest) {
5
+ return handleChatCompletion(request, {}, 'OpenAI API Chat Completion (Global)');
6
+ }
@@ -0,0 +1,65 @@
1
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
+ import { validateApiKey } from '@/src/utils/validateApiKey';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+
5
+ /**
6
+ * GET /api/openai/v1/models
7
+ *
8
+ * Lists all available agents as models for the OpenAI-compatible API.
9
+ */
10
+ export async function GET(request: NextRequest) {
11
+ // Validate API key explicitly (in addition to middleware)
12
+ const apiKeyValidation = await validateApiKey(request);
13
+ if (!apiKeyValidation.isValid) {
14
+ return NextResponse.json(
15
+ {
16
+ error: {
17
+ message: apiKeyValidation.error || 'Invalid API key',
18
+ type: 'authentication_error',
19
+ },
20
+ },
21
+ { status: 401 },
22
+ );
23
+ }
24
+
25
+ try {
26
+ const collection = await $provideAgentCollectionForServer();
27
+ const agentNames = await collection.listAgents();
28
+
29
+ const models = agentNames.map((agentName) => ({
30
+ id: agentName,
31
+ object: 'model',
32
+ created: Math.floor(Date.now() / 1000), // We don't have creation date readily available in listAgents
33
+ owned_by: 'promptbook',
34
+ permission: [
35
+ {
36
+ id: `modelperm-${agentName}`,
37
+ object: 'model_permission',
38
+ created: Math.floor(Date.now() / 1000),
39
+ allow_create_engine: false,
40
+ allow_sampling: true,
41
+ allow_logprobs: false,
42
+ allow_search_indices: false,
43
+ allow_view: true,
44
+ allow_fine_tuning: false,
45
+ organization: '*',
46
+ group: null,
47
+ is_blocking: false,
48
+ },
49
+ ],
50
+ root: agentName,
51
+ parent: null,
52
+ }));
53
+
54
+ return NextResponse.json({
55
+ object: 'list',
56
+ data: models,
57
+ });
58
+ } catch (error) {
59
+ console.error('Error in models listing handler:', error);
60
+ return NextResponse.json(
61
+ { error: { message: (error as Error).message || 'Internal Server Error', type: 'server_error' } },
62
+ { status: 500 },
63
+ );
64
+ }
65
+ }
@@ -1,7 +1,9 @@
1
1
  import { notFound } from 'next/navigation';
2
- import ReactMarkdown from 'react-markdown';
3
2
  import { BookCommitment } from '../../../../../../src/commitments/_base/BookCommitment';
4
3
  import { getVisibleCommitmentDefinitions } from '../../../utils/getVisibleCommitmentDefinitions';
4
+ import { PrintButton } from '../../../components/PrintButton/PrintButton';
5
+ import { PrintHeader } from '../../../components/PrintHeader/PrintHeader';
6
+ import { DocumentationContent } from '../../../components/DocumentationContent/DocumentationContent';
5
7
 
6
8
  type DocPageProps = {
7
9
  params: Promise<{
@@ -25,38 +27,16 @@ export default async function DocPage(props: DocPageProps) {
25
27
 
26
28
  return (
27
29
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
30
+ <PrintButton />
31
+
28
32
  <div className="container mx-auto px-4 py-16">
29
- <div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
30
- <div className="p-8 border-b border-gray-100 bg-gray-50/50">
31
- <div className="flex items-center gap-4 mb-4">
32
- <h1 className="text-4xl font-bold text-gray-900 tracking-tight">
33
- <span className="mr-3">{primary.icon}</span>
34
- {primary.type}
35
- {aliases.length > 0 && (
36
- <span className="text-gray-400 font-normal ml-4 text-2xl">
37
- / {aliases.join(' / ')}
38
- </span>
39
- )}
40
- </h1>
41
- <span className="px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700">
42
- Commitment
43
- </span>
44
- </div>
45
- {primary.description && (
46
- <p className="text-xl text-gray-600 leading-relaxed max-w-3xl">
47
- {primary.description}
48
- </p>
49
- )}
50
- </div>
51
-
52
- <div className="p-8">
53
- <article className="prose prose-lg prose-slate max-w-none prose-headings:font-bold prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-p:text-gray-600 prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200 prose-pre:text-gray-800">
54
- <ReactMarkdown>
55
- {primary.documentation}
56
- </ReactMarkdown>
57
- </article>
58
- </div>
59
- </div>
33
+ <PrintHeader title={primary.type} />
34
+
35
+ <DocumentationContent
36
+ primary={primary}
37
+ aliases={aliases}
38
+ isPrintOnly={false}
39
+ />
60
40
  </div>
61
41
  </div>
62
42
  );
@@ -1,33 +1,58 @@
1
1
  import Link from 'next/link';
2
2
  import { Card } from '../../components/Homepage/Card';
3
3
  import { Section } from '../../components/Homepage/Section';
4
+ import { OpenMojiIcon } from '../../components/OpenMojiIcon/OpenMojiIcon';
4
5
  import { getVisibleCommitmentDefinitions } from '../../utils/getVisibleCommitmentDefinitions';
6
+ import { PrintButton } from '../../components/PrintButton/PrintButton';
7
+ import { PrintHeader } from '../../components/PrintHeader/PrintHeader';
8
+ import { DocumentationContent } from '../../components/DocumentationContent/DocumentationContent';
5
9
 
6
10
  export default function DocsPage() {
7
11
  const groupedCommitments = getVisibleCommitmentDefinitions();
8
12
 
9
13
  return (
10
14
  <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
15
+ <PrintButton />
16
+
11
17
  <div className="container mx-auto px-4 py-16">
12
- <Section title="Documentation">
18
+ <PrintHeader title="Full Documentation" />
19
+
20
+ {/* Screen view: Cards */}
21
+ <div className="print:hidden">
22
+ <Section title="Documentation">
23
+ {groupedCommitments.map(({ primary, aliases }) => (
24
+ <Link key={primary.type} href={`/docs/${primary.type}`} className="block h-full group">
25
+ <Card className="h-full group-hover:border-blue-500 transition-colors">
26
+ <h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
27
+ <OpenMojiIcon icon={primary.icon} className="mr-2" />
28
+ {primary.type}
29
+ {aliases.length > 0 && (
30
+ <span className="text-gray-400 font-normal text-lg">
31
+ {' / '}
32
+ {aliases.join(' / ')}
33
+ </span>
34
+ )}
35
+ </h3>
36
+ {primary.description && <p className="text-gray-600 line-clamp-3">{primary.description}</p>}
37
+ </Card>
38
+ </Link>
39
+ ))}
40
+ </Section>
41
+ </div>
42
+
43
+ {/* Print view: Full Content */}
44
+ <div className="hidden print:block space-y-12">
13
45
  {groupedCommitments.map(({ primary, aliases }) => (
14
- <Link key={primary.type} href={`/docs/${primary.type}`} className="block h-full group">
15
- <Card className="h-full group-hover:border-blue-500 transition-colors">
16
- <h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
17
- <span className="mr-2">{primary.icon}</span>
18
- {primary.type}
19
- {aliases.length > 0 && (
20
- <span className="text-gray-400 font-normal text-lg">
21
- {' / '}
22
- {aliases.join(' / ')}
23
- </span>
24
- )}
25
- </h3>
26
- {primary.description && <p className="text-gray-600 line-clamp-3">{primary.description}</p>}
27
- </Card>
28
- </Link>
46
+ <div key={primary.type} className="break-inside-avoid page-break-after-always">
47
+ <DocumentationContent
48
+ primary={primary}
49
+ aliases={aliases}
50
+ isPrintOnly={true}
51
+ />
52
+ <hr className="my-8 border-gray-200" />
53
+ </div>
29
54
  ))}
30
- </Section>
55
+ </div>
31
56
  </div>
32
57
  </div>
33
58
  );