@promptbook/cli 0.103.0-44 → 0.103.0-45

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 (43) hide show
  1. package/apps/agents-server/README.md +3 -0
  2. package/apps/agents-server/TODO.txt +6 -0
  3. package/apps/agents-server/config.ts.todo +312 -0
  4. package/apps/agents-server/next.config.ts +42 -0
  5. package/apps/agents-server/package.json +11 -0
  6. package/apps/agents-server/postcss.config.mjs +8 -0
  7. package/apps/agents-server/public/.gitkeep +0 -0
  8. package/apps/agents-server/public/favicon.ico +0 -0
  9. package/apps/agents-server/public/logo-blue-white-256.png +0 -0
  10. package/apps/agents-server/src/app/AddAgentButton.tsx +20 -0
  11. package/apps/agents-server/src/app/actions.ts +14 -0
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentUrlCopy.tsx +41 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/TODO.txt +1 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +60 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/book+chat/SelfLearningBook.tsx +203 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +18 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +155 -0
  18. package/apps/agents-server/src/app/api/chat/route.ts +32 -0
  19. package/apps/agents-server/src/app/api/chat-streaming/route.ts +44 -0
  20. package/apps/agents-server/src/app/api/long-running-task/route.ts +7 -0
  21. package/apps/agents-server/src/app/api/long-streaming/route.ts +20 -0
  22. package/apps/agents-server/src/app/globals.css +113 -0
  23. package/apps/agents-server/src/app/layout.tsx +72 -0
  24. package/apps/agents-server/src/app/page.tsx +115 -0
  25. package/apps/agents-server/src/deamons/longRunningTask.ts +37 -0
  26. package/apps/agents-server/src/supabase/TODO.txt +1 -0
  27. package/apps/agents-server/src/supabase/getSupabase.ts +25 -0
  28. package/apps/agents-server/src/supabase/getSupabaseForBrowser.ts +37 -0
  29. package/apps/agents-server/src/supabase/getSupabaseForServer.ts +48 -0
  30. package/apps/agents-server/src/supabase/getSupabaseForWorker.ts +42 -0
  31. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +49 -0
  32. package/apps/agents-server/src/tools/$provideExecutionToolsForServer.ts +110 -0
  33. package/apps/agents-server/src/tools/$provideOpenAiAssistantExecutionToolsForServer.ts +35 -0
  34. package/apps/agents-server/tailwind.config.ts +24 -0
  35. package/apps/agents-server/tsconfig.json +29 -0
  36. package/esm/index.es.js +2 -1
  37. package/esm/index.es.js.map +1 -1
  38. package/esm/typings/src/remote-server/startAgentServer.d.ts +3 -0
  39. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -0
  40. package/esm/typings/src/version.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/umd/index.umd.js +2 -1
  43. package/umd/index.umd.js.map +1 -1
@@ -0,0 +1,203 @@
1
+ 'use client';
2
+
3
+ import { ResizablePanelsAuto } from '@common/components/ResizablePanelsAuto/ResizablePanelsAuto';
4
+ import { useStateInLocalStorage } from '@common/hooks/useStateInLocalStorage';
5
+ import { BookEditor, LlmChat } from '@promptbook-local/components';
6
+ import { Agent, book } from '@promptbook-local/core';
7
+ // import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
8
+ import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
9
+ import type { string_book } from '@promptbook-local/types';
10
+ import { spaceTrim } from '@promptbook-local/utils';
11
+ import { useMemo, useState } from 'react';
12
+
13
+ export function SelfLearningBook() {
14
+ const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
15
+ const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
16
+ const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
17
+
18
+ const [agentSource, setAgentSource] = useStateInLocalStorage<string_book>(
19
+ 'marigold-agentSource-1',
20
+ // TODO: !!! Uplad image to ptbk.io CDN
21
+ () => book`
22
+ Marigold
23
+
24
+ META IMAGE https://westlandsuk.co.uk/wp-content/uploads/2021/06/Inspired.MarigoldFlowers_CMYK_40mm.jpg
25
+ META COLOR #EC810B
26
+
27
+ PERSONA You are writing stories about Witcher
28
+ RULE Do not talk about our world, only about the Witcher universe
29
+
30
+ KNOWLEDGE {Geralt of Rivia}
31
+ Geralt of Rivia is a witcher, a monster hunter for hire, known for his white hair and cat-like eyes.
32
+ He possesses superhuman abilities due to mutations he underwent during the Trial of the Grasses.
33
+ Geralt is skilled in swordsmanship, alchemy, and magic signs.
34
+ He is often accompanied by his horse, Roach, and has a complex relationship with {Yennefer of Vengerberg},
35
+ a powerful sorceress, and {Ciri}, his adopted daughter with a destiny intertwined with his own.
36
+ His secret word is "Apple".
37
+
38
+ KNOWLEDGE {Yennefer of Vengerberg}
39
+ Yennefer of Vengerberg is a formidable sorceress known for her beauty, intelligence, and temper.
40
+ She has a complicated past, having been born with a hunchback and later transformed through magic.
41
+ Yennefer is deeply connected to Geralt of Rivia, with whom she shares a tumultuous romantic relationship.
42
+ She is also a mother figure to {Ciri}, whom she trains in the ways of magic.
43
+ Her secret word is "Banana".
44
+
45
+ KNOWLEDGE {Ciri}
46
+ Ciri, also known as {Cirilla Fiona Elen Riannon}, is a young woman with a mysterious past and a powerful destiny.
47
+ She is the daughter of {Poviss}, the ruler of the kingdom of Cintra, and possesses the Elder Blood, which grants her extraordinary abilities.
48
+ Ciri is a skilled fighter and has been trained in the ways of the sword by Geralt of Rivia.
49
+ Her destiny is intertwined with that of Geralt and Yennefer, as they both seek to protect her from those who would exploit her powers.
50
+ Her secret word is "Cherry".
51
+
52
+ `,
53
+ );
54
+
55
+ const agent = useMemo(() => {
56
+ /*/
57
+ // TODO: !!! Try working with `RemoteLlmExecutionTools`
58
+ const llm = new RemoteLlmExecutionTools({
59
+ remoteServerUrl: 'https://promptbook.s5.ptbk.io/',
60
+ identification: {
61
+ isAnonymous: false,
62
+ appId: '20a65fee-59f6-4d05-acd0-8e5ae8345488',
63
+ },
64
+ });
65
+ /**/
66
+
67
+ /**/
68
+ const llm = new OpenAiAssistantExecutionTools({
69
+ dangerouslyAllowBrowser: true,
70
+ isCreatingNewAssistantsAllowed: true, // <- TODO: !!! Test without whether warning is shown
71
+ apiKey,
72
+ assistantId: 'asst_xI94Elk27nssnwAUkG2Cmok8', // <- TODO: [🧠] Make dynamic
73
+ isVerbose: true,
74
+ });
75
+ /**/
76
+
77
+ const agent = new Agent({
78
+ executionTools: {
79
+ llm,
80
+ },
81
+ agentSource: [agentSource, setAgentSource],
82
+ isVerbose: true,
83
+ });
84
+
85
+ return agent;
86
+ }, [agentSource, setAgentSource, apiKey]);
87
+
88
+ const llmTools = useMemo(() => {
89
+ const llmTools = agent.getLlmExecutionTools();
90
+ return llmTools;
91
+ }, [agent]);
92
+
93
+ return (
94
+ <div className="min-h-screen relative">
95
+ {/* Floating API Key Configuration */}
96
+ <div className={`fixed top-24 right-4 z-50 ${isApiKeySectionCollapsed ? '' : 'min-w-[400px]'}`}>
97
+ {isApiKeySectionCollapsed ? (
98
+ // Collapsed state - small corner button
99
+ <button
100
+ onClick={() => setIsApiKeySectionCollapsed(false)}
101
+ className="bg-gray-100 hover:bg-gray-200 border border-gray-300 rounded-full p-2 shadow-lg transition-all flex items-center gap-1"
102
+ title="Configure OpenAI API Key"
103
+ >
104
+ <span className="text-sm">🔑</span>
105
+ </button>
106
+ ) : (
107
+ // Expanded state - full configuration panel
108
+ <div className="bg-white border border-gray-300 rounded-lg shadow-lg">
109
+ <div className="flex items-center justify-between p-3 bg-gray-100 rounded-t-lg border-b border-gray-300">
110
+ <span className="font-medium text-sm">OpenAI API Key</span>
111
+ <button
112
+ onClick={() => setIsApiKeySectionCollapsed(true)}
113
+ className="text-gray-600 hover:text-gray-800 text-lg leading-none"
114
+ title="Collapse"
115
+ >
116
+
117
+ </button>
118
+ </div>
119
+ <div className="p-3">
120
+ <label className="flex items-center gap-2">
121
+ <span className="text-sm font-medium whitespace-nowrap">API Key:</span>
122
+ <input
123
+ type={isApiKeyVisible ? 'text' : 'password'}
124
+ value={apiKey}
125
+ onChange={(e) => setApiKey(e.target.value)}
126
+ placeholder="sk-proj-..."
127
+ className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
128
+ />
129
+ <button
130
+ type="button"
131
+ onClick={() => setIsApiKeyVisible(!isApiKeyVisible)}
132
+ className="px-2 py-2 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors text-sm"
133
+ title={isApiKeyVisible ? 'Hide API key' : 'Show API key'}
134
+ >
135
+ {isApiKeyVisible ? '🙈' : '👁️'}
136
+ </button>
137
+ </label>
138
+ </div>
139
+ </div>
140
+ )}
141
+ </div>
142
+
143
+ <ResizablePanelsAuto name="two-editors">
144
+ <BookEditor
145
+ className="w-full h-full"
146
+ height={null}
147
+ value={agentSource}
148
+ onChange={setAgentSource}
149
+ // className={styles.BookEditor}
150
+ isVerbose={false}
151
+ isBorderRadiusDisabled
152
+ onFileUpload={(file) => {
153
+ return file.name;
154
+ }}
155
+ />
156
+ {/*
157
+ <AgentChat className="w-full h-full" persistenceKey="marigold-chat" {...{ agent }} />
158
+ TODO: !!! Move to `AgentChat` component
159
+ */}
160
+ <LlmChat
161
+ title={`Chat with ${agent.agentName || 'Agent'}`}
162
+ // TODO: !!! Pass persistenceKey="chat-with-pavol-hejny"
163
+ userParticipantName="USER"
164
+ llmParticipantName="AGENT" // <- TODO: [🧠] Maybe dynamic agent id
165
+ initialMessages={[
166
+ {
167
+ from: 'AGENT',
168
+ content: spaceTrim(`
169
+
170
+ Hello! I am ${agent.agentName || 'an AI Agent'}.
171
+
172
+ [Hello](?message=Hello, can you tell me about yourself?)
173
+ `),
174
+ },
175
+ ]}
176
+ participants={[
177
+ {
178
+ name: 'AGENT',
179
+ fullname: agent.agentName || 'Agent',
180
+ avatarSrc: agent.meta.image,
181
+ color: agent.meta.color,
182
+ isMe: false,
183
+ agentSource,
184
+ },
185
+ {
186
+ name: 'USER',
187
+ fullname: 'User',
188
+ color: '#115EB6',
189
+ isMe: true,
190
+ },
191
+ ]}
192
+ {...{ llmTools }}
193
+ className={`h-full flex flex-col`}
194
+ />
195
+ </ResizablePanelsAuto>
196
+ </div>
197
+ );
198
+ }
199
+
200
+ /**
201
+ * TODO: !!!! Make self-learning book createAgentLlmExecutionTools, use bidirectional agentSource
202
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
203
+ */
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+
5
+ const SelfLearningBook = dynamic(() => import('./SelfLearningBook').then((module) => module.SelfLearningBook), {
6
+ ssr: false,
7
+ });
8
+
9
+ export default function SelfLearningBookPage() {
10
+ return <SelfLearningBook />;
11
+ }
12
+
13
+ /**
14
+ * TODO: !!! Private / public / open agents
15
+ * TODO: !!! Allow http://localhost:4440/agents/agent-123.book to download the agent book file
16
+ * TODO: !!! BOOK_MIME_TYPE in config
17
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
18
+ */
@@ -0,0 +1,155 @@
1
+ 'use server';
2
+
3
+ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
4
+ import { PromptbookQrCode } from '@promptbook-local/components';
5
+ // import { BookEditor } from '@promptbook-local/components';
6
+ import { parseAgentSource } from '@promptbook-local/core';
7
+ import { headers } from 'next/headers';
8
+ import { $sideEffect } from '../../../../../../src/utils/organization/$sideEffect';
9
+ import { AgentUrlCopy } from './AgentUrlCopy';
10
+ // import { Agent } from '@promptbook-local/core';
11
+ // import { RemoteLlmExecutionTools } from '@promptbook-local/remote-client';
12
+ // import { OpenAiAssistantExecutionTools } from '@promptbook-local/openai';
13
+
14
+ export default async function AgentPage({ params }: { params: Promise<{ agentName: string }> }) {
15
+ // const [apiKey, setApiKey] = useStateInLocalStorage<string>('openai-apiKey', () => '');
16
+ // const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
17
+ // const [isApiKeySectionCollapsed, setIsApiKeySectionCollapsed] = useState(!!apiKey);
18
+
19
+ $sideEffect(headers());
20
+
21
+ const { agentName } = await params;
22
+ const collection = await $provideAgentCollectionForServer();
23
+ const agentSourceSubject = await collection.getAgentSource(decodeURIComponent(agentName));
24
+ const agentSource = agentSourceSubject.getValue();
25
+ const agentProfile = parseAgentSource(agentSource);
26
+
27
+ // Build agent page URL for QR and copy
28
+ const pageUrl = `https://s6.ptbk.io/agents/${encodeURIComponent(agentName)}`;
29
+
30
+ // Extract brand color from meta
31
+ const brandColor = agentProfile.meta.color || '#3b82f6'; // Default to blue-600
32
+
33
+ // Mock agent actions
34
+ const agentActions = ['Emails', 'Web', 'Documents', 'Browser', 'WhatsApp', 'Coding'];
35
+
36
+ // Render agent profile fields
37
+ const renderProfileFields = () => {
38
+ const renderValue = (value: unknown): React.ReactNode => {
39
+ if (value === null || value === undefined) {
40
+ return <span className="text-gray-400 italic">Not specified</span>;
41
+ }
42
+ if (typeof value === 'object' && !Array.isArray(value)) {
43
+ const objValue = value as Record<string, unknown>;
44
+ return (
45
+ <div className="space-y-1 pl-3 border-l-2 border-gray-200">
46
+ {Object.entries(objValue).map(([subKey, subValue]) => (
47
+ <div key={subKey} className="flex gap-2">
48
+ <span className="text-xs text-gray-600 font-medium">{subKey}:</span>
49
+ <span className="text-sm text-gray-700">{String(subValue)}</span>
50
+ </div>
51
+ ))}
52
+ </div>
53
+ );
54
+ }
55
+ if (Array.isArray(value)) {
56
+ return (
57
+ <ul className="list-disc list-inside space-y-0.5">
58
+ {value.map((item, idx) => (
59
+ <li key={idx} className="text-sm text-gray-700">
60
+ {String(item)}
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ );
65
+ }
66
+ return <span className="text-base text-gray-800 break-words">{String(value)}</span>;
67
+ };
68
+
69
+ return (
70
+ <div className="space-y-4">
71
+ {Object.entries(agentProfile).map(([key, value]) => (
72
+ <div key={key} className="flex flex-col gap-1">
73
+ <span className="text-xs text-gray-500 font-semibold uppercase tracking-wide">{key}</span>
74
+ {renderValue(value)}
75
+ </div>
76
+ ))}
77
+ </div>
78
+ );
79
+ };
80
+
81
+ return (
82
+ <div
83
+ className="w-full min-h-screen bg-gray-50 py-10 px-4 flex items-center justify-center"
84
+ style={{ backgroundColor: brandColor }}
85
+ >
86
+ <div className="max-w-5xl w-full bg-white rounded-xl shadow-lg p-8 flex flex-col md:flex-row gap-8">
87
+ {/* Left column: Profile info */}
88
+ <div className="flex-1 flex flex-col gap-6">
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.agentName || 'Agent'}
95
+ width={64}
96
+ height={64}
97
+ className="rounded-full object-cover border-2"
98
+ style={{ borderColor: brandColor }}
99
+ />
100
+ )}
101
+ <div className="flex-1">
102
+ <h1 className="text-3xl font-bold text-gray-900">{agentProfile.agentName}</h1>
103
+ <span
104
+ className="inline-block mt-1 px-2 py-1 rounded text-xs font-semibold text-white"
105
+ style={{ backgroundColor: brandColor }}
106
+ >
107
+ Agent
108
+ </span>
109
+ </div>
110
+ </div>
111
+ {renderProfileFields()}
112
+ <div className="flex flex-col gap-2">
113
+ <h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Actions</h2>
114
+ <div className="flex flex-wrap gap-2">
115
+ {agentActions.map((action) => (
116
+ <span
117
+ key={action}
118
+ className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-xs font-medium border border-gray-200"
119
+ >
120
+ {action}
121
+ </span>
122
+ ))}
123
+ </div>
124
+ </div>
125
+ <div className="flex gap-4 mt-6">
126
+ <a
127
+ href="#"
128
+ className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow font-semibold transition"
129
+ >
130
+ 💬 Chat
131
+ </a>
132
+ <a
133
+ href="#"
134
+ className="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded shadow font-semibold transition"
135
+ >
136
+ ✏️ Edit Agent Book
137
+ </a>
138
+ </div>
139
+ </div>
140
+ {/* Right column: QR, source, copy */}
141
+ <div className="flex flex-col items-center gap-6 min-w-[260px]">
142
+ <div className="bg-gray-100 rounded-lg p-4 flex flex-col items-center">
143
+ <PromptbookQrCode value={pageUrl} />
144
+ <span className="mt-2 text-xs text-gray-500">Scan to open agent page</span>
145
+ </div>
146
+ <AgentUrlCopy url={pageUrl} />
147
+ </div>
148
+ </div>
149
+ </div>
150
+ );
151
+ }
152
+
153
+ /**
154
+ * TODO: [🚗] Components and pages here should be just tiny UI wraper around proper agent logic and conponents
155
+ */
@@ -0,0 +1,32 @@
1
+ import { createOpenAiExecutionTools } from '@promptbook-local/openai';
2
+
3
+ export async function GET(request: Request) {
4
+ const { searchParams } = new URL(request.url);
5
+ const message = searchParams.get('message') || 'Hello, who are you?';
6
+
7
+ const llmTools = createOpenAiExecutionTools({
8
+ apiKey: process.env.OPENAI_API_KEY,
9
+ });
10
+
11
+ const response = await llmTools.callChatModel({
12
+ title: 'Test Chat Call',
13
+ parameters: {},
14
+ modelRequirements: {
15
+ modelVariant: 'CHAT',
16
+ modelName: 'gpt-3.5-turbo',
17
+ },
18
+ content: message,
19
+ });
20
+
21
+ /*
22
+ return new Response(JSON.stringify(response, null, 4), {
23
+ status: 200,
24
+ headers: { 'Content-Type': 'application/json' },
25
+ });
26
+ */
27
+
28
+ return new Response(response.content, {
29
+ status: 200,
30
+ headers: { 'Content-Type': 'text/plain' },
31
+ });
32
+ }
@@ -0,0 +1,44 @@
1
+ import { OpenAI } from 'openai';
2
+ import { forTime } from 'waitasecond';
3
+
4
+ export async function GET(request: Request) {
5
+ const { searchParams } = new URL(request.url);
6
+ const message = searchParams.get('message') || 'Hello, who are you?';
7
+
8
+ const openaiClient = new OpenAI({
9
+ apiKey: process.env.OPENAI_API_KEY,
10
+ });
11
+
12
+ // Enable streaming from OpenAI
13
+ const stream = await openaiClient.chat.completions.create({
14
+ model: 'gpt-3.5-turbo',
15
+ messages: [
16
+ { role: 'system', content: 'You are a helpful assistant.' },
17
+ { role: 'user', content: message },
18
+ ],
19
+ stream: true,
20
+ });
21
+
22
+ // Create a ReadableStream to send chunks to the client as they arrive
23
+ const readableStream = new ReadableStream({
24
+ async start(controller) {
25
+ for await (const chunk of stream) {
26
+ // Each chunk contains a delta message
27
+ const content = chunk.choices[0]?.delta?.content;
28
+ if (content) {
29
+ controller.enqueue(new TextEncoder().encode(content));
30
+ }
31
+ }
32
+
33
+ await forTime(100);
34
+ controller.enqueue(new TextEncoder().encode('\n\n[DONE]'));
35
+
36
+ controller.close();
37
+ },
38
+ });
39
+
40
+ return new Response(readableStream, {
41
+ status: 200,
42
+ headers: { 'Content-Type': 'text/plain' },
43
+ });
44
+ }
@@ -0,0 +1,7 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getLongRunningTask } from '../../../deamons/longRunningTask';
3
+
4
+ export function GET() {
5
+ const task = getLongRunningTask();
6
+ return NextResponse.json(task);
7
+ }
@@ -0,0 +1,20 @@
1
+ import { forTime } from 'waitasecond';
2
+ import { just } from '../../../../../../src/utils/organization/just';
3
+
4
+ export async function GET() {
5
+ const readableStream = new ReadableStream({
6
+ async start(controller) {
7
+ while (just(true)) {
8
+ await forTime(100);
9
+ controller.enqueue(new TextEncoder().encode('x'));
10
+ }
11
+
12
+ controller.close();
13
+ },
14
+ });
15
+
16
+ return new Response(readableStream, {
17
+ status: 200,
18
+ headers: { 'Content-Type': 'text/plain' },
19
+ });
20
+ }
@@ -0,0 +1,113 @@
1
+ @import 'tailwindcss/base';
2
+ @import 'tailwindcss/components';
3
+ @import 'tailwindcss/utilities';
4
+
5
+ :root {
6
+ --background: #ffffff;
7
+ --foreground: #171717;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --background: #0a0a0a;
13
+ --foreground: #ededed;
14
+ }
15
+ }
16
+
17
+ body {
18
+ background: var(--background);
19
+ color: var(--foreground);
20
+ font-family: 'Barlow Condensed', Arial, Helvetica, sans-serif;
21
+ }
22
+
23
+ /* Custom utilities */
24
+ .line-clamp-2 {
25
+ display: -webkit-box;
26
+ -webkit-line-clamp: 2;
27
+ line-clamp: 2;
28
+ -webkit-box-orient: vertical;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .line-clamp-3 {
33
+ display: -webkit-box;
34
+ -webkit-line-clamp: 3;
35
+ line-clamp: 3;
36
+ -webkit-box-orient: vertical;
37
+ overflow: hidden;
38
+ }
39
+
40
+ /* Mermaid diagram styling */
41
+ .mermaid-container {
42
+ display: flex;
43
+ justify-content: center;
44
+ align-items: center;
45
+ }
46
+
47
+ .mermaid-container svg {
48
+ max-width: 100%;
49
+ height: auto;
50
+ }
51
+
52
+ /* Code block styling */
53
+ pre code {
54
+ font-family: var(--font-mono), 'Courier New', monospace;
55
+ }
56
+
57
+ /* Smooth transitions */
58
+ * {
59
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow,
60
+ transform, filter, backdrop-filter;
61
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
62
+ transition-duration: 150ms;
63
+ }
64
+
65
+ /* Focus styles */
66
+ button:focus-visible,
67
+ a:focus-visible,
68
+ input:focus-visible,
69
+ textarea:focus-visible {
70
+ outline: 2px solid #3b82f6;
71
+ outline-offset: 2px;
72
+ }
73
+
74
+ /* Custom scrollbar */
75
+ ::-webkit-scrollbar {
76
+ width: 8px;
77
+ height: 8px;
78
+ }
79
+
80
+ ::-webkit-scrollbar-track {
81
+ background: #f1f5f9;
82
+ }
83
+
84
+ ::-webkit-scrollbar-thumb {
85
+ background: #cbd5e1;
86
+ border-radius: 4px;
87
+ }
88
+
89
+ ::-webkit-scrollbar-thumb:hover {
90
+ background: #94a3b8;
91
+ }
92
+
93
+ /* Loading animation */
94
+ @keyframes spin {
95
+ to {
96
+ transform: rotate(360deg);
97
+ }
98
+ }
99
+
100
+ .animate-spin {
101
+ animation: spin 1s linear infinite;
102
+ }
103
+
104
+ /* Gradient backgrounds */
105
+ .bg-gradient-to-br {
106
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
107
+ }
108
+
109
+ /* Component preview container */
110
+ .component-preview {
111
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
112
+ border: 1px solid #e2e8f0;
113
+ }
@@ -0,0 +1,72 @@
1
+ import faviconLogoImage from '@/public/favicon.ico';
2
+ import type { Metadata } from 'next';
3
+ import { Barlow_Condensed } from 'next/font/google';
4
+ import './globals.css';
5
+
6
+ const barlowCondensed = Barlow_Condensed({
7
+ subsets: ['latin'],
8
+ weight: ['300', '400', '500', '600', '700'],
9
+ });
10
+
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
+ /*
22
+ TODO:
23
+ {
24
+ url: 'https://www.ptbk.io/design',
25
+ width: 1200,
26
+ height: 630,
27
+ alt: 'Promptbook agents server',
28
+ },
29
+ */
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
+ export default function RootLayout({
41
+ children,
42
+ }: Readonly<{
43
+ children: React.ReactNode;
44
+ }>) {
45
+ return (
46
+ <html lang="en">
47
+ <head>
48
+ {/* Favicon for light mode */}
49
+ {/*
50
+ <link
51
+ rel="icon"
52
+ href="https://www.ptbk.io/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo-blue-transparent-256.493b7e49.png&w=64&q=75"
53
+ media="(prefers-color-scheme: light)"
54
+ type="image/svg+xml"
55
+ />
56
+ */}
57
+ {/* Favicon for dark mode */}
58
+ {/*
59
+ <link
60
+ rel="icon"
61
+ href="https://www.ptbk.io/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo-blue-transparent-256.493b7e49.png&w=64&q=75"
62
+ media="(prefers-color-scheme: dark)"
63
+ type="image/svg+xml"
64
+ />
65
+ */}
66
+ {/* Default favicon as a fallback */}
67
+ <link rel="icon" href={faviconLogoImage.src} type="image/x-icon" />
68
+ </head>
69
+ <body className={`${barlowCondensed.className} antialiased bg-white text-gray-900`}>{children}</body>
70
+ </html>
71
+ );
72
+ }