@promptbook/cli 0.104.0-1 → 0.104.0-3

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 (128) hide show
  1. package/apps/agents-server/next.config.ts +2 -2
  2. package/apps/agents-server/package.json +7 -3
  3. package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
  4. package/apps/agents-server/public/swagger.json +115 -0
  5. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
  6. package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
  7. package/apps/agents-server/src/app/AddAgentButton.tsx +3 -3
  8. package/apps/agents-server/src/app/actions.ts +17 -5
  9. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
  10. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
  11. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
  12. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
  13. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
  14. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
  15. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +2 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +18 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  18. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  19. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
  21. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +1 -1
  22. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
  23. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  24. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  25. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  27. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
  28. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +214 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  30. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  31. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +1 -1
  32. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +2 -2
  33. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +12 -6
  34. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +87 -0
  35. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  36. package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +10 -12
  37. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +19 -0
  38. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +41 -0
  39. package/apps/agents-server/src/app/api/agents/route.ts +28 -3
  40. package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
  41. package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
  42. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  43. package/apps/agents-server/src/app/api/images/[filename]/route.ts +107 -0
  44. package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
  45. package/apps/agents-server/src/app/api/upload/route.ts +128 -45
  46. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  47. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  48. package/apps/agents-server/src/app/globals.css +140 -33
  49. package/apps/agents-server/src/app/layout.tsx +27 -22
  50. package/apps/agents-server/src/app/page.tsx +50 -4
  51. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  52. package/apps/agents-server/src/app/recycle-bin/page.tsx +25 -41
  53. package/apps/agents-server/src/app/sitemap.xml/route.ts +6 -3
  54. package/apps/agents-server/src/app/swagger/page.tsx +14 -0
  55. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +9 -98
  56. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
  57. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  58. package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
  59. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  60. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  61. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  62. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  63. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  64. package/apps/agents-server/src/components/Header/Header.tsx +106 -40
  65. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +104 -20
  66. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +72 -12
  67. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +50 -0
  68. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  69. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  70. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  71. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  72. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  73. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  74. package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
  75. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  76. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  77. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  78. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  79. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  80. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  81. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  82. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  83. package/apps/agents-server/src/database/schema.ts +109 -0
  84. package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
  85. package/apps/agents-server/src/middleware.ts +19 -23
  86. package/apps/agents-server/src/tools/$provideCdnForServer.ts +6 -1
  87. package/apps/agents-server/src/utils/auth.ts +117 -17
  88. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  89. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  90. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  91. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
  92. package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
  93. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +21 -0
  94. package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
  95. package/esm/index.es.js +194 -34
  96. package/esm/index.es.js.map +1 -1
  97. package/esm/typings/src/_packages/types.index.d.ts +8 -2
  98. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
  99. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  100. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  101. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  102. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  103. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +7 -11
  104. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +2 -2
  105. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  106. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +13 -7
  107. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +6 -0
  108. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  109. package/esm/typings/src/commitments/index.d.ts +2 -1
  110. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  111. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  112. package/esm/typings/src/types/Message.d.ts +49 -0
  113. package/esm/typings/src/types/typeAliases.d.ts +12 -0
  114. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  115. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  116. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  117. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  118. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  119. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  120. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  121. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  122. package/esm/typings/src/version.d.ts +1 -1
  123. package/package.json +1 -1
  124. package/umd/index.umd.js +200 -40
  125. package/umd/index.umd.js.map +1 -1
  126. package/apps/agents-server/package-lock.json +0 -27
  127. package/apps/agents-server/public/fonts/download-font.js +0 -22
  128. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
@@ -54,10 +54,7 @@ function getMessagePreview(message: unknown, maxLength = 120): string {
54
54
  }
55
55
 
56
56
  if (typeof message === 'object') {
57
- const content =
58
- (message as { content?: unknown }).content ??
59
- (message as { text?: unknown }).text ??
60
- message;
57
+ const content = (message as { content?: unknown }).content ?? (message as { text?: unknown }).text ?? message;
61
58
 
62
59
  let text: string;
63
60
 
@@ -303,11 +300,12 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
303
300
  const message = row.message as { role?: string; content?: string };
304
301
  const role = (message.role || 'USER').toUpperCase();
305
302
  return {
303
+ // channel: 'PROMPTBOOK_CHAT',
306
304
  id: String(row.id),
307
- from: role === 'USER' ? 'USER' : 'ASSISTANT',
305
+ sender: role === 'USER' ? 'USER' : 'ASSISTANT',
308
306
  content: message.content || JSON.stringify(message),
309
307
  isComplete: true,
310
- date: new Date(row.createdAt),
308
+ createdAt: new Date(row.createdAt),
311
309
  } satisfies ChatMessage;
312
310
  });
313
311
  }, [items, viewMode]);
@@ -317,17 +315,9 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
317
315
  <div>
318
316
  {total > 0 ? (
319
317
  <>
320
- Showing{' '}
321
- <span className="font-semibold">
322
- {Math.min((page - 1) * pageSize + 1, total)}
323
- </span>{' '}
324
- –{' '}
325
- <span className="font-semibold">
326
- {Math.min(page * pageSize, total)}
327
- </span>{' '}
328
- of{' '}
329
- <span className="font-semibold">{total}</span>{' '}
330
- messages
318
+ Showing <span className="font-semibold">{Math.min((page - 1) * pageSize + 1, total)}</span> –{' '}
319
+ <span className="font-semibold">{Math.min(page * pageSize, total)}</span> of{' '}
320
+ <span className="font-semibold">{total}</span> messages
331
321
  </>
332
322
  ) : (
333
323
  'No messages'
@@ -403,97 +393,90 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
403
393
  </a>
404
394
  </div>
405
395
  <div>
406
- <div className="text-xl font-semibold text-gray-900">
407
- {total.toLocaleString()}
408
- </div>
409
- <div className="text-xs uppercase tracking-wide text-gray-400">
410
- Total messages
411
- </div>
396
+ <div className="text-xl font-semibold text-gray-900">{total.toLocaleString()}</div>
397
+ <div className="text-xs uppercase tracking-wide text-gray-400">Total messages</div>
412
398
  </div>
413
399
  </div>
414
400
  </div>
415
401
  <Card>
416
- <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
417
- <form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
418
- <div className="flex flex-col gap-1">
419
- <label htmlFor="search" className="text-sm font-medium text-gray-700">
420
- Search
421
- </label>
422
- <input
423
- id="search"
424
- type="text"
425
- value={searchInput}
426
- onChange={(event) => setSearchInput(event.target.value)}
427
- placeholder="Search by agent name, URL or IP"
428
- className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
429
- />
430
- </div>
431
- <button
432
- type="submit"
433
- className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
402
+ <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
403
+ <form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
404
+ <div className="flex flex-col gap-1">
405
+ <label htmlFor="search" className="text-sm font-medium text-gray-700">
406
+ Search
407
+ </label>
408
+ <input
409
+ id="search"
410
+ type="text"
411
+ value={searchInput}
412
+ onChange={(event) => setSearchInput(event.target.value)}
413
+ placeholder="Search by agent name, URL or IP"
414
+ className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
415
+ />
416
+ </div>
417
+ <button
418
+ type="submit"
419
+ className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
420
+ >
421
+ Apply
422
+ </button>
423
+ </form>
424
+
425
+ <div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
426
+ <div className="flex flex-col gap-1">
427
+ <label htmlFor="agentFilter" className="text-sm font-medium text-gray-700">
428
+ Agent filter
429
+ </label>
430
+ <select
431
+ id="agentFilter"
432
+ value={agentName}
433
+ onChange={handleAgentChange}
434
+ className="w-full md:w-64 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
434
435
  >
435
- Apply
436
- </button>
437
- </form>
438
-
439
- <div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
440
- <div className="flex flex-col gap-1">
441
- <label htmlFor="agentFilter" className="text-sm font-medium text-gray-700">
442
- Agent filter
443
- </label>
444
- <select
445
- id="agentFilter"
446
- value={agentName}
447
- onChange={handleAgentChange}
448
- className="w-full md:w-64 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
449
- >
450
- <option value="">All agents</option>
451
- {agents.map((agent) => (
452
- <option key={agent.agentName} value={agent.agentName}>
453
- {agent.fullname || agent.agentName}
454
- </option>
455
- ))}
456
- </select>
457
- {agentsLoading && (
458
- <span className="text-xs text-gray-400">Loading agents…</span>
459
- )}
460
- </div>
461
-
462
- <div className="flex flex-col gap-1">
463
- <label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
464
- Page size
465
- </label>
466
- <select
467
- id="pageSize"
468
- value={pageSize}
469
- onChange={handlePageSizeChange}
470
- className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
471
- >
472
- <option value={10}>10</option>
473
- <option value={20}>20</option>
474
- <option value={50}>50</option>
475
- <option value={100}>100</option>
476
- </select>
477
- </div>
436
+ <option value="">All agents</option>
437
+ {agents.map((agent) => (
438
+ <option key={agent.agentName} value={agent.agentName}>
439
+ {agent.fullname || agent.agentName}
440
+ </option>
441
+ ))}
442
+ </select>
443
+ {agentsLoading && <span className="text-xs text-gray-400">Loading agents…</span>}
478
444
  </div>
479
- </div>
480
445
 
481
- {agentName && (
482
- <div className="mt-4 flex items-center justify-between gap-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3">
483
- <p className="text-sm text-amber-800">
484
- Showing chat history for agent{' '}
485
- <span className="font-semibold break-all">{agentName}</span>.
486
- </p>
487
- <button
488
- type="button"
489
- onClick={handleClearAgentHistory}
490
- className="inline-flex items-center justify-center rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50"
446
+ <div className="flex flex-col gap-1">
447
+ <label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
448
+ Page size
449
+ </label>
450
+ <select
451
+ id="pageSize"
452
+ value={pageSize}
453
+ onChange={handlePageSizeChange}
454
+ className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
491
455
  >
492
- Clear history for this agent
493
- </button>
456
+ <option value={10}>10</option>
457
+ <option value={20}>20</option>
458
+ <option value={50}>50</option>
459
+ <option value={100}>100</option>
460
+ </select>
494
461
  </div>
495
- )}
496
- </Card>
462
+ </div>
463
+ </div>
464
+
465
+ {agentName && (
466
+ <div className="mt-4 flex items-center justify-between gap-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3">
467
+ <p className="text-sm text-amber-800">
468
+ Showing chat history for agent <span className="font-semibold break-all">{agentName}</span>.
469
+ </p>
470
+ <button
471
+ type="button"
472
+ onClick={handleClearAgentHistory}
473
+ className="inline-flex items-center justify-center rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50"
474
+ >
475
+ Clear history for this agent
476
+ </button>
477
+ </div>
478
+ )}
479
+ </Card>
497
480
 
498
481
  {viewMode === 'chat' ? (
499
482
  <div className="bg-white rounded-lg shadow border border-gray-200 overflow-hidden flex flex-col">
@@ -505,22 +488,14 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
505
488
  isSaveButtonEnabled={true}
506
489
  />
507
490
  </div>
508
- <div className="p-4 bg-gray-50 border-t border-gray-200">
509
- {pagination}
510
- </div>
491
+ <div className="p-4 bg-gray-50 border-t border-gray-200">{pagination}</div>
511
492
  </div>
512
493
  ) : (
513
494
  <Card>
514
495
  <div className="flex items-center justify-between mb-4">
515
- <h2 className="text-lg font-medium text-gray-900">
516
- Messages ({total})
517
- </h2>
496
+ <h2 className="text-lg font-medium text-gray-900">Messages ({total})</h2>
518
497
  </div>
519
- {error && (
520
- <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">
521
- {error}
522
- </div>
523
- )}
498
+ {error && <div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">{error}</div>}
524
499
 
525
500
  {loading && items.length === 0 ? (
526
501
  <div className="py-8 text-center text-gray-500">Loading chat history…</div>
@@ -555,27 +530,13 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
555
530
  )}
556
531
  </button>
557
532
  </th>
558
- <th className="px-4 py-3 text-left font-medium text-gray-500">
559
- Role
560
- </th>
561
- <th className="px-4 py-3 text-left font-medium text-gray-500">
562
- Message
563
- </th>
564
- <th className="px-4 py-3 text-left font-medium text-gray-500">
565
- URL
566
- </th>
567
- <th className="px-4 py-3 text-left font-medium text-gray-500">
568
- IP
569
- </th>
570
- <th className="px-4 py-3 text-left font-medium text-gray-500">
571
- Language
572
- </th>
573
- <th className="px-4 py-3 text-left font-medium text-gray-500">
574
- Platform
575
- </th>
576
- <th className="px-4 py-3 text-right font-medium text-gray-500">
577
- Actions
578
- </th>
533
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Role</th>
534
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Message</th>
535
+ <th className="px-4 py-3 text-left font-medium text-gray-500">URL</th>
536
+ <th className="px-4 py-3 text-left font-medium text-gray-500">IP</th>
537
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Language</th>
538
+ <th className="px-4 py-3 text-left font-medium text-gray-500">Platform</th>
539
+ <th className="px-4 py-3 text-right font-medium text-gray-500">Actions</th>
579
540
  </tr>
580
541
  </thead>
581
542
  <tbody className="divide-y divide-gray-200 bg-white">
@@ -596,9 +557,7 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
596
557
  </div>
597
558
  </td>
598
559
  <td className="max-w-xs px-4 py-3 text-gray-500">
599
- <div className="truncate text-xs">
600
- {row.url || '-'}
601
- </div>
560
+ <div className="truncate text-xs">{row.url || '-'}</div>
602
561
  </td>
603
562
  <td className="whitespace-nowrap px-4 py-3 text-gray-500">
604
563
  {row.ip || '-'}
@@ -607,9 +566,7 @@ export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps)
607
566
  {row.language || '-'}
608
567
  </td>
609
568
  <td className="max-w-xs px-4 py-3 text-gray-500">
610
- <div className="truncate text-xs">
611
- {row.platform || '-'}
612
- </div>
569
+ <div className="truncate text-xs">{row.platform || '-'}</div>
613
570
  </td>
614
571
  <td className="whitespace-nowrap px-4 py-3 text-right text-xs font-medium">
615
572
  <button
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { FileText, Hash, Image, Shield, ToggleLeft, Type, Upload } from 'lucide-react';
3
+ import { upload } from '@vercel/blob/client';
4
+ import { FileTextIcon, HashIcon, ImageIcon, ShieldIcon, ToggleLeftIcon, TypeIcon, Upload } from 'lucide-react';
4
5
  import { useEffect, useRef, useState } from 'react';
5
6
  import { metadataDefaults, MetadataType } from '../../../database/metadataDefaults';
6
7
 
@@ -148,19 +149,19 @@ export function MetadataClient() {
148
149
  const getTypeIcon = (type?: MetadataType) => {
149
150
  switch (type) {
150
151
  case 'TEXT_SINGLE_LINE':
151
- return <Type className="w-4 h-4" />;
152
+ return <TypeIcon className="w-4 h-4" />;
152
153
  case 'TEXT':
153
- return <FileText className="w-4 h-4" />;
154
+ return <FileTextIcon className="w-4 h-4" />;
154
155
  case 'NUMBER':
155
- return <Hash className="w-4 h-4" />;
156
+ return <HashIcon className="w-4 h-4" />;
156
157
  case 'BOOLEAN':
157
- return <ToggleLeft className="w-4 h-4" />;
158
+ return <ToggleLeftIcon className="w-4 h-4" />;
158
159
  case 'IMAGE_URL':
159
- return <Image className="w-4 h-4" />;
160
+ return <ImageIcon className="w-4 h-4" />;
160
161
  case 'IP_RANGE':
161
- return <Shield className="w-4 h-4" />;
162
+ return <ShieldIcon className="w-4 h-4" />;
162
163
  default:
163
- return <Type className="w-4 h-4" />;
164
+ return <TypeIcon className="w-4 h-4" />;
164
165
  }
165
166
  };
166
167
 
@@ -185,26 +186,29 @@ export function MetadataClient() {
185
186
 
186
187
  try {
187
188
  setIsUploading(true);
188
- const formData = new FormData();
189
- formData.append('file', file);
190
189
 
191
- const response = await fetch('/api/upload', {
192
- method: 'POST',
193
- body: formData,
190
+ // Build the full path including prefix and user/files directory
191
+ const pathPrefix = process.env.NEXT_PUBLIC_CDN_PATH_PREFIX || '';
192
+ const uploadPath = pathPrefix ? `${pathPrefix}/user/files/${file.name}` : `user/files/${file.name}`;
193
+
194
+ // Upload directly to Vercel Blob using client upload
195
+ const blob = await upload(uploadPath, file, {
196
+ access: 'public',
197
+ handleUploadUrl: '/api/upload',
198
+ clientPayload: JSON.stringify({
199
+ purpose: formState.key || 'METADATA_IMAGE',
200
+ contentType: file.type,
201
+ }),
194
202
  });
195
203
 
196
- if (!response.ok) {
197
- throw new Error(`Failed to upload file: ${response.statusText}`);
198
- }
199
-
200
- const { fileUrl: longFileUrl } = await response.json();
204
+ const fileUrl = blob.url;
201
205
 
202
206
  const LONG_URL = `${process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!}/${process.env
203
207
  .NEXT_PUBLIC_CDN_PATH_PREFIX!}/user/files/`;
204
208
  const SHORT_URL = `https://ptbk.io/k/`;
205
209
  // <- TODO: [🌍] Unite this logic in one place
206
210
 
207
- const shortFileUrl = longFileUrl.split(LONG_URL).join(SHORT_URL);
211
+ const shortFileUrl = fileUrl.split(LONG_URL).join(SHORT_URL);
208
212
  setFormState((prev) => ({ ...prev, value: shortFileUrl }));
209
213
  } catch (err) {
210
214
  setError(err instanceof Error ? err.message : 'Failed to upload image');
@@ -3,7 +3,7 @@
3
3
  import { usePromise } from '@common/hooks/usePromise';
4
4
  import { AgentChat } from '@promptbook-local/components';
5
5
  import { RemoteAgent } from '@promptbook-local/core';
6
- import { useCallback, useMemo } from 'react';
6
+ import { useCallback, useEffect, useMemo } from 'react';
7
7
  import { string_agent_url } from '../../../../../../src/types/typeAliases';
8
8
 
9
9
  type AgentChatWrapperProps = {
@@ -58,6 +58,20 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
58
58
  [agent, agentUrl],
59
59
  );
60
60
 
61
+ // Remove the 'message' query parameter from URL after auto-executing a message
62
+ useEffect(() => {
63
+ if (autoExecuteMessage && typeof window !== 'undefined') {
64
+ // Wait for the message to be processed, then remove the query parameter
65
+ const timer = setTimeout(() => {
66
+ const url = new URL(window.location.href);
67
+ url.searchParams.delete('message');
68
+ window.history.replaceState({}, '', url.toString());
69
+ }, 1000); // 1 second delay to ensure message processing is complete
70
+
71
+ return () => clearTimeout(timer);
72
+ }
73
+ }, [autoExecuteMessage]);
74
+
61
75
  if (!agent) {
62
76
  return <>{/* <- TODO: [🐱‍🚀] <PromptbookLoading /> */}</>;
63
77
  }
@@ -2,9 +2,11 @@
2
2
 
3
3
  import { TODO_any } from '@promptbook-local/types';
4
4
  import {
5
+ CodeIcon,
5
6
  CopyIcon,
6
7
  CopyPlusIcon,
7
8
  DownloadIcon,
9
+ FileTextIcon,
8
10
  MailIcon,
9
11
  MessageCircleQuestionIcon,
10
12
  MessageSquareIcon,
@@ -13,10 +15,12 @@ import {
13
15
  QrCodeIcon,
14
16
  SmartphoneIcon,
15
17
  SquareSplitHorizontalIcon,
18
+ TrashIcon,
16
19
  } from 'lucide-react';
17
20
  import { Barlow_Condensed } from 'next/font/google';
18
21
  import { useCallback, useEffect, useRef, useState } from 'react';
19
- import { string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
22
+ import { string_agent_permanent_id, string_data_url, string_url_image } from '../../../../../../src/types/typeAliases';
23
+ import { deleteAgent } from '../../recycle-bin/actions';
20
24
  import { getAgentLinks } from './agentLinks';
21
25
 
22
26
  type BeforeInstallPromptEvent = Event & {
@@ -33,6 +37,7 @@ const barlowCondensed = Barlow_Condensed({
33
37
  type AgentOptionsMenuProps = {
34
38
  agentName: string;
35
39
  derivedAgentName: string;
40
+ permanentId?: string_agent_permanent_id;
36
41
  agentUrl: string;
37
42
  agentEmail: string;
38
43
  brandColorHex: string;
@@ -44,11 +49,10 @@ type AgentOptionsMenuProps = {
44
49
  export function AgentOptionsMenu({
45
50
  agentName,
46
51
  derivedAgentName,
52
+ permanentId,
47
53
  agentUrl,
48
54
  agentEmail,
49
- brandColorHex,
50
55
  isAdmin = false,
51
- backgroundImage,
52
56
  onShowQrCode,
53
57
  }: AgentOptionsMenuProps) {
54
58
  const [isOpen, setIsOpen] = useState(false);
@@ -115,7 +119,7 @@ export function AgentOptionsMenu({
115
119
  }
116
120
  };
117
121
 
118
- const links = getAgentLinks(agentName);
122
+ const links = getAgentLinks(permanentId || agentName);
119
123
  const editBookLink = links.find((l) => l.title === 'Edit Book')!;
120
124
  const integrationLink = links.find((l) => l.title === 'Integration')!;
121
125
  const historyLink = links.find((l) => l.title === 'History & Feedback')!;
@@ -128,13 +132,29 @@ export function AgentOptionsMenu({
128
132
  const handleUpdateUrl = () => {
129
133
  if (
130
134
  window.confirm(
131
- `Are you sure you want to change the agent URL from "/agents/${agentName}" to "/agents/${derivedAgentName}"?`
135
+ `Are you sure you want to change the agent URL from "/agents/${agentName}" to "/agents/${derivedAgentName}"?`,
132
136
  )
133
137
  ) {
134
138
  window.location.href = updateUrlHref;
135
139
  }
136
140
  };
137
141
 
142
+ const handleDeleteAgent = async () => {
143
+ if (
144
+ window.confirm(
145
+ `Are you sure you want to delete the agent "${agentName}"? This action can be undone by restoring it from the recycle bin.`,
146
+ )
147
+ ) {
148
+ try {
149
+ await deleteAgent(agentName);
150
+ window.location.href = '/';
151
+ } catch (error) {
152
+ console.error('Failed to delete agent:', error);
153
+ alert('Failed to delete agent. Please try again.');
154
+ }
155
+ }
156
+ };
157
+
138
158
  const menuItems = [
139
159
  ...(showUpdateUrl
140
160
  ? [
@@ -166,6 +186,18 @@ export function AgentOptionsMenu({
166
186
  icon: editBookLink.icon,
167
187
  label: editBookLink.title,
168
188
  },
189
+ {
190
+ type: 'link' as const,
191
+ href: `/agents/${encodeURIComponent(agentName)}/system-message`,
192
+ icon: FileTextIcon,
193
+ label: 'System Message',
194
+ },
195
+ {
196
+ type: 'link' as const,
197
+ href: `/agents/${encodeURIComponent(agentName)}/code`,
198
+ icon: CodeIcon,
199
+ label: 'View Code',
200
+ },
169
201
  { type: 'divider' as const },
170
202
  {
171
203
  type: 'link' as const,
@@ -243,6 +275,12 @@ export function AgentOptionsMenu({
243
275
  icon: DownloadIcon,
244
276
  label: 'Export Agent',
245
277
  },
278
+ {
279
+ type: 'action' as const,
280
+ icon: TrashIcon,
281
+ label: 'Delete Agent',
282
+ onClick: handleDeleteAgent,
283
+ },
246
284
  // {
247
285
  // type: 'link' as const,
248
286
  // href: backgroundImage,
@@ -297,12 +335,16 @@ export function AgentOptionsMenu({
297
335
  }
298
336
  }}
299
337
  className={`flex items-center gap-3 px-4 py-2.5 w-full text-left transition-colors
300
- ${item.highlight
301
- ? 'bg-yellow-100 text-yellow-900 font-bold hover:bg-yellow-200'
302
- : 'text-gray-700 hover:bg-gray-50'}
338
+ ${
339
+ item.highlight
340
+ ? 'bg-yellow-100 text-yellow-900 font-bold hover:bg-yellow-200'
341
+ : 'text-gray-700 hover:bg-gray-50'
342
+ }
303
343
  `}
304
344
  >
305
- <item.icon className={`w-4 h-4 ${item.highlight ? 'text-yellow-700' : 'text-gray-500'}`} />
345
+ <item.icon
346
+ className={`w-4 h-4 ${item.highlight ? 'text-yellow-700' : 'text-gray-500'}`}
347
+ />
306
348
  <span className="text-sm font-medium">{item.label}</span>
307
349
  </button>
308
350
  );
@@ -3,10 +3,14 @@
3
3
  import { usePromise } from '@common/hooks/usePromise';
4
4
  import { Chat } from '@promptbook-local/components';
5
5
  import { RemoteAgent } from '@promptbook-local/core';
6
+ import { string_book } from '@promptbook-local/types';
6
7
  import { useRouter } from 'next/navigation';
7
- import { useCallback, useMemo } from 'react';
8
+ import { useCallback, useMemo, useState } from 'react';
8
9
  import spaceTrim from 'spacetrim';
9
10
  import { string_agent_url, string_color } from '../../../../../../src/types/typeAliases';
11
+ import { keepUnused } from '../../../../../../src/utils/organization/keepUnused';
12
+ import { $createAgentFromBookAction } from '../../../app/actions';
13
+ import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
10
14
 
11
15
  type AgentProfileChatProps = {
12
16
  agentUrl: string_agent_url;
@@ -14,10 +18,21 @@ type AgentProfileChatProps = {
14
18
  fullname: string;
15
19
  brandColorHex: string_color;
16
20
  avatarSrc: string;
21
+ isDeleted?: boolean;
17
22
  };
18
23
 
19
- export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex, avatarSrc }: AgentProfileChatProps) {
24
+ export function AgentProfileChat({
25
+ agentUrl,
26
+ agentName,
27
+ fullname,
28
+ brandColorHex,
29
+ avatarSrc,
30
+ isDeleted = false,
31
+ }: AgentProfileChatProps) {
20
32
  const router = useRouter();
33
+ const [isCreatingAgent, setIsCreatingAgent] = useState(false);
34
+
35
+ keepUnused(isCreatingAgent);
21
36
 
22
37
  const agentPromise = useMemo(
23
38
  () =>
@@ -38,6 +53,24 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
38
53
  [agentName, router],
39
54
  );
40
55
 
56
+ const handleCreateAgent = useCallback(
57
+ async (bookContent: string) => {
58
+ setIsCreatingAgent(true);
59
+ try {
60
+ const { permanentId } = await $createAgentFromBookAction(bookContent as string_book);
61
+ if (permanentId) {
62
+ router.push(`/agents/${permanentId}`);
63
+ }
64
+ } catch (error) {
65
+ console.error('Failed to create agent:', error);
66
+ alert('Failed to create agent. Please try again.');
67
+ } finally {
68
+ setIsCreatingAgent(false);
69
+ }
70
+ },
71
+ [router],
72
+ );
73
+
41
74
  const initialMessage = useMemo(() => {
42
75
  if (!agent) {
43
76
  return 'Loading...';
@@ -52,6 +85,15 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
52
85
  );
53
86
  }, [agent, fullname, agentName]);
54
87
 
88
+ // If agent is deleted, show banner instead of chat
89
+ if (isDeleted) {
90
+ return (
91
+ <div className="w-full min-h-[350px] md:min-h-[500px] flex items-center justify-center">
92
+ <DeletedAgentBanner message="This agent has been deleted. You can restore it from the Recycle Bin." />
93
+ </div>
94
+ );
95
+ }
96
+
55
97
  // If agent is not loaded yet, we can show a skeleton or just the default Chat structure
56
98
  // But to match "same initial message", we need the agent loaded or at least the default fallback.
57
99
  // The fallback above matches AgentChat.tsx default.
@@ -72,14 +114,15 @@ export function AgentProfileChat({ agentUrl, agentName, fullname, brandColorHex,
72
114
  ]}
73
115
  messages={[
74
116
  {
75
- from: 'AGENT',
117
+ sender: 'AGENT',
76
118
  content: initialMessage,
77
- date: new Date(),
119
+ createdAt: new Date(),
78
120
  id: 'initial-message',
79
121
  isComplete: true,
80
122
  },
81
123
  ]}
82
124
  onMessage={handleMessage}
125
+ onCreateAgent={handleCreateAgent}
83
126
  isSaveButtonEnabled={false}
84
127
  isCopyButtonEnabled={false}
85
128
  className="bg-transparent"