@promptbook/cli 0.103.0-66 → 0.103.0-67

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.
@@ -8,4 +8,4 @@ TODO: [🧠][🚙] `AgentXxx` vs `AgentsXxx` naming convention
8
8
 
9
9
 
10
10
  TODO: !!!! No dependencies should be here
11
- TODO: !!!! Check react and next for critical error
11
+ TODO: !!!! Check react and next for react2shell critical error
@@ -29,7 +29,9 @@ const config = ConfigChecker.from({
29
29
  *
30
30
  * Note: When `SERVERS` are used, this URL will be overridden by the server URL.
31
31
  */
32
- export const NEXT_PUBLIC_SITE_URL = config.get('NEXT_PUBLIC_SITE_URL').url().required().value;
32
+ export const NEXT_PUBLIC_SITE_URL = config
33
+ .get('NEXT_PUBLIC_SITE_URL')
34
+ .url()./* <- TODO: !!!! Is it ok not to be required().*/ value;
33
35
 
34
36
  /**
35
37
  * [â™ī¸] Vercel environment: "development" | "preview" | "production"
@@ -57,7 +57,7 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
57
57
  // The fallback above matches AgentChat.tsx default.
58
58
 
59
59
  return (
60
- <div className="w-full h-[400px] md:h-[500px]">
60
+ <div className="w-full h-[calc(100dvh-300px)] min-h-[350px] md:h-[500px]">
61
61
  <Chat
62
62
  title={`Chat with ${fullname}`}
63
63
  participants={[
@@ -208,11 +208,14 @@ export function AgentProfile(props: AgentProfileProps) {
208
208
  )}
209
209
 
210
210
  {/* Main profile content */}
211
- <div className="relative z-10 flex flex-col md:flex-row items-center md:items-start gap-8 md:gap-12 max-w-5xl w-full">
211
+ <div className="relative z-10 grid grid-cols-[auto_1fr] gap-x-6 gap-y-4 md:gap-12 max-w-5xl w-full items-start">
212
212
  {/* Agent image card (Flippable) */}
213
- <div className="flex-shrink-0 perspective-1000 group" style={{ perspective: '1000px' }}>
213
+ <div
214
+ className="flex-shrink-0 perspective-1000 group row-start-1 col-start-1 md:row-span-3"
215
+ style={{ perspective: '1000px' }}
216
+ >
214
217
  <div
215
- className="relative w-72 md:w-80 transition-all duration-700 transform-style-3d cursor-pointer"
218
+ className="relative w-24 md:w-80 transition-all duration-700 transform-style-3d cursor-pointer"
216
219
  style={{
217
220
  aspectRatio: '1 / 1.618', // Golden Ratio
218
221
  transformStyle: 'preserve-3d',
@@ -222,7 +225,7 @@ export function AgentProfile(props: AgentProfileProps) {
222
225
  >
223
226
  {/* Front of Card (Image) */}
224
227
  <div
225
- className="absolute inset-0 w-full h-full backface-hidden rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm"
228
+ className="absolute inset-0 w-full h-full backface-hidden rounded-lg md:rounded-3xl shadow-lg md:shadow-2xl overflow-hidden backdrop-blur-sm"
226
229
  style={{
227
230
  backfaceVisibility: 'hidden',
228
231
  backgroundColor: brandColorDarkHex,
@@ -234,7 +237,7 @@ export function AgentProfile(props: AgentProfileProps) {
234
237
  <img src={imageUrl} alt={fullname} className="w-full h-full object-cover" />
235
238
  ) : (
236
239
  <div
237
- className="w-full h-full flex items-center justify-center text-8xl font-bold text-white/80"
240
+ className="w-full h-full flex items-center justify-center text-3xl md:text-8xl font-bold text-white/80"
238
241
  style={{ backgroundColor: brandColorDarkHex }}
239
242
  >
240
243
  {fullname.charAt(0).toUpperCase()}
@@ -242,14 +245,14 @@ export function AgentProfile(props: AgentProfileProps) {
242
245
  )}
243
246
 
244
247
  {/* Flip hint icon */}
245
- <div className="absolute bottom-4 right-4 bg-black/30 p-2 rounded-full text-white/80 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity">
246
- <RepeatIcon className="w-5 h-5" />
248
+ <div className="absolute bottom-2 md:bottom-4 right-2 md:right-4 bg-black/30 p-1 md:p-2 rounded-full text-white/80 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity">
249
+ <RepeatIcon className="w-3 h-3 md:w-5 md:h-5" />
247
250
  </div>
248
251
  </div>
249
252
 
250
253
  {/* Back of Card (QR Code) */}
251
254
  <div
252
- className="absolute inset-0 w-full h-full backface-hidden rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm flex flex-col items-center justify-center p-6"
255
+ className="absolute inset-0 w-full h-full backface-hidden rounded-lg md:rounded-3xl shadow-lg md:shadow-2xl overflow-hidden backdrop-blur-sm flex flex-col items-center justify-center p-2 md:p-6"
253
256
  style={{
254
257
  backfaceVisibility: 'hidden',
255
258
  transform: 'rotateY(180deg)',
@@ -269,18 +272,18 @@ export function AgentProfile(props: AgentProfileProps) {
269
272
  </div>
270
273
 
271
274
  {/* Flip hint icon */}
272
- <div className="absolute bottom-4 right-4 bg-black/10 p-2 rounded-full text-black/50 backdrop-blur-md">
273
- <RepeatIcon className="w-5 h-5" />
275
+ <div className="absolute bottom-2 md:bottom-4 right-2 md:right-4 bg-black/10 p-1 md:p-2 rounded-full text-black/50 backdrop-blur-md">
276
+ <RepeatIcon className="w-3 h-3 md:w-5 md:h-5" />
274
277
  </div>
275
278
  </div>
276
279
  </div>
277
280
  </div>
278
281
 
279
- {/* Agent info */}
280
- <div className="flex flex-col items-center md:items-start text-center md:text-left gap-6">
282
+ {/* Agent info - Header Area */}
283
+ <div className="row-start-1 col-start-2 flex flex-col justify-center md:justify-start h-full md:h-auto gap-1 md:gap-6">
281
284
  {/* Agent name with custom font */}
282
285
  <h1
283
- className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 tracking-tight"
286
+ className="text-2xl md:text-5xl lg:text-6xl font-bold text-gray-900 tracking-tight leading-tight"
284
287
  style={{
285
288
  textShadow: '0 2px 20px rgba(255, 255, 255, 0.5)',
286
289
  }}
@@ -289,20 +292,22 @@ export function AgentProfile(props: AgentProfileProps) {
289
292
  </h1>
290
293
 
291
294
  {/* Short description */}
292
- <p className="text-lg md:text-xl text-gray-700 max-w-lg leading-relaxed font-medium">
295
+ <p className="text-sm md:text-xl text-gray-700 max-w-lg leading-relaxed font-medium line-clamp-3 md:line-clamp-none">
293
296
  {personaDescription}
294
297
  </p>
298
+ </div>
295
299
 
296
- {/* Chat */}
297
- <div className="w-full">{children}</div>
298
-
299
- {/* Secondary Actions */}
300
- {!isHeadless && (
301
- <div className="flex flex-wrap justify-center md:justify-start items-center gap-4 md:gap-6 mt-2">
302
- {actions}
303
- </div>
304
- )}
300
+ {/* Chat Area */}
301
+ <div className="col-span-2 md:col-span-1 md:col-start-2 w-full mt-2 md:mt-0">
302
+ {children}
305
303
  </div>
304
+
305
+ {/* Secondary Actions */}
306
+ {!isHeadless && (
307
+ <div className="col-span-2 md:col-span-1 md:col-start-2 flex flex-wrap justify-center md:justify-start items-center gap-4 md:gap-6 mt-2">
308
+ {actions}
309
+ </div>
310
+ )}
306
311
  </div>
307
312
 
308
313
  {/* Subtle gradient overlay at bottom */}
@@ -14,10 +14,22 @@ let cdn: IIFilesStorageWithCdn | null = null;
14
14
  export function $provideCdnForServer(): IIFilesStorageWithCdn {
15
15
  if (!cdn) {
16
16
  cdn = new VercelBlobStorage({
17
- token: process.env.BLOB_READ_WRITE_TOKEN!,
17
+ token: process.env.VERCEL_BLOB_READ_WRITE_TOKEN!,
18
18
  pathPrefix: process.env.NEXT_PUBLIC_CDN_PATH_PREFIX!,
19
19
  cdnPublicUrl: new URL(process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!),
20
20
  });
21
+
22
+ /*
23
+ cdn = new DigitalOceanSpaces({
24
+ bucket: process.env.CDN_BUCKET!,
25
+ pathPrefix: process.env.NEXT_PUBLIC_CDN_PATH_PREFIX!,
26
+ endpoint: process.env.CDN_ENDPOINT!,
27
+ accessKeyId: process.env.CDN_ACCESS_KEY_ID!,
28
+ secretAccessKey: process.env.CDN_SECRET_ACCESS_KEY!,
29
+ cdnPublicUrl: new URL(process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!),
30
+ gzip: true,
31
+ });
32
+ */
21
33
  }
22
34
 
23
35
  return cdn;
@@ -0,0 +1,119 @@
1
+ import { GetObjectCommand, PutObjectCommand, PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3';
2
+ import { NotYetImplementedError } from '@promptbook-local/core';
3
+ import { gzip, ungzip } from 'node-gzip';
4
+ import { TODO_USE } from '../../../../../../src/utils/organization/TODO_USE';
5
+ import { validateMimeType } from '../../validators/validateMimeType';
6
+ import type { IFile, IIFilesStorageWithCdn } from '../interfaces/IFilesStorage';
7
+
8
+ type IDigitalOceanSpacesConfig = {
9
+ readonly bucket: string;
10
+ readonly pathPrefix: string;
11
+ readonly endpoint: string;
12
+ readonly accessKeyId: string;
13
+ readonly secretAccessKey: string;
14
+ readonly cdnPublicUrl: URL;
15
+ readonly gzip: boolean;
16
+
17
+ // TODO: [â›ŗī¸] Probbably prefix should be in this config not on the consumer side
18
+ };
19
+
20
+ export class DigitalOceanSpaces implements IIFilesStorageWithCdn {
21
+ public get cdnPublicUrl() {
22
+ return this.config.cdnPublicUrl;
23
+ }
24
+
25
+ private s3: S3Client;
26
+
27
+ public constructor(private readonly config: IDigitalOceanSpacesConfig) {
28
+ this.s3 = new S3Client({
29
+ region: 'auto',
30
+ endpoint: 'https://' + config.endpoint,
31
+ credentials: {
32
+ accessKeyId: config.accessKeyId,
33
+ secretAccessKey: config.secretAccessKey,
34
+ },
35
+ });
36
+ }
37
+
38
+ public getItemUrl(key: string): URL {
39
+ return new URL(this.config.pathPrefix + '/' + key, this.cdnPublicUrl);
40
+ }
41
+
42
+ public async getItem(key: string): Promise<IFile | null> {
43
+ const parameters = {
44
+ Bucket: this.config.bucket,
45
+ Key: this.config.pathPrefix + '/' + key,
46
+ };
47
+
48
+ try {
49
+ const { Body, ContentType, ContentEncoding } = await this.s3.send(new GetObjectCommand(parameters));
50
+
51
+ // const blob = new Blob([await Body?.transformToByteArray()!]);
52
+
53
+ if (ContentEncoding === 'gzip') {
54
+ return {
55
+ type: validateMimeType(ContentType),
56
+ data: await ungzip(await Body!.transformToByteArray()),
57
+ };
58
+ } else {
59
+ return {
60
+ type: validateMimeType(ContentType),
61
+ data: (await Body!.transformToByteArray()) as Buffer,
62
+ };
63
+ }
64
+ } catch (error) {
65
+ if (error instanceof Error && error.name.match(/^NoSuchKey/)) {
66
+ return null;
67
+ } else {
68
+ throw error;
69
+ }
70
+ }
71
+ }
72
+
73
+ public async removeItem(key: string): Promise<void> {
74
+ TODO_USE(key);
75
+ throw new NotYetImplementedError(`DigitalOceanSpaces.removeItem is not implemented yet`);
76
+ }
77
+
78
+ public async setItem(key: string, file: IFile): Promise<void> {
79
+ // TODO: Put putObjectRequestAdditional into processedFile
80
+ const putObjectRequestAdditional: Partial<PutObjectCommandInput> = {};
81
+
82
+ let processedFile: IFile;
83
+ if (this.config.gzip) {
84
+ const gzipped = await gzip(file.data);
85
+ const sizePercentageAfterCompression = gzipped.byteLength / file.data.byteLength;
86
+ if (sizePercentageAfterCompression < 0.7) {
87
+ // consolex.log(`Gzipping ${key} (${Math.floor(sizePercentageAfterCompression * 100)}%)`);
88
+ processedFile = { ...file, data: gzipped };
89
+ putObjectRequestAdditional.ContentEncoding = 'gzip';
90
+ } else {
91
+ processedFile = file;
92
+ // consolex.log(`NOT Gzipping ${key} (${Math.floor(sizePercentageAfterCompression * 100)}%)`);
93
+ }
94
+ } else {
95
+ processedFile = file;
96
+ }
97
+
98
+ const uploadResult = await this.s3.send(
99
+ new PutObjectCommand({
100
+ Bucket: this.config.bucket,
101
+ Key: this.config.pathPrefix + '/' + key,
102
+ ContentType: processedFile.type,
103
+ ...putObjectRequestAdditional,
104
+ Body: processedFile.data,
105
+ // TODO: Public read access / just private to extending class
106
+ ACL: 'public-read',
107
+ }),
108
+ );
109
+
110
+ if (!uploadResult.ETag) {
111
+ throw new Error(`Upload result does not contain ETag`);
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * TODO: Implement Read-only mode
118
+ * TODO: [â˜šī¸] Unite with `PromptbookStorage` and move to `/src/...`
119
+ */
@@ -51,12 +51,13 @@ export class VercelBlobStorage implements IIFilesStorageWithCdn {
51
51
 
52
52
  public async setItem(key: string, file: IFile): Promise<void> {
53
53
  const path = this.config.pathPrefix ? `${this.config.pathPrefix}/${key}` : key;
54
-
54
+
55
55
  await put(path, file.data, {
56
56
  access: 'public',
57
57
  addRandomSuffix: false,
58
58
  contentType: file.type,
59
59
  token: this.config.token,
60
+ allowOverwrite: true, // <- TODO: This is inefficient, we should check first if the file exists and only then decide to overwrite or not
60
61
  // Note: We rely on Vercel Blob for compression
61
62
  });
62
63
  }
package/esm/index.es.js CHANGED
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
47
47
  * @generated
48
48
  * @see https://github.com/webgptorg/promptbook
49
49
  */
50
- const PROMPTBOOK_ENGINE_VERSION = '0.103.0-66';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-67';
51
51
  /**
52
52
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
53
53
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.103.0-65`).
18
+ * It follows semantic versioning (e.g., `0.103.0-66`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.103.0-66",
3
+ "version": "0.103.0-67",
4
4
  "description": "Promptbook: Turn your company's scattered knowledge into AI ready books",
5
5
  "private": false,
6
6
  "sideEffects": false,
package/umd/index.umd.js CHANGED
@@ -56,7 +56,7 @@
56
56
  * @generated
57
57
  * @see https://github.com/webgptorg/promptbook
58
58
  */
59
- const PROMPTBOOK_ENGINE_VERSION = '0.103.0-66';
59
+ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-67';
60
60
  /**
61
61
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
62
62
  * Note: [💞] Ignore a discrepancy between file name and entity name