@alpaca-editor/core 1.0.3942 → 1.0.3943

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 (308) hide show
  1. package/.prettierrc +3 -0
  2. package/build.css +3 -0
  3. package/components.json +21 -0
  4. package/dist/editor/ContentTree.d.ts +2 -1
  5. package/dist/editor/ContentTree.js +23 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldActionsOverlay.js +0 -2
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  9. package/dist/editor/ScrollingContentTree.js +1 -1
  10. package/dist/editor/ScrollingContentTree.js.map +1 -1
  11. package/dist/editor/Titlebar.js +1 -1
  12. package/dist/editor/Titlebar.js.map +1 -1
  13. package/dist/editor/ai/GhostWriter.js +24 -3
  14. package/dist/editor/ai/GhostWriter.js.map +1 -1
  15. package/dist/editor/client/EditorClient.js +7 -7
  16. package/dist/editor/client/EditorClient.js.map +1 -1
  17. package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
  18. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  19. package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
  20. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  21. package/dist/editor/menubar/PageSelector.js +116 -65
  22. package/dist/editor/menubar/PageSelector.js.map +1 -1
  23. package/dist/editor/page-viewer/EditorForm.js +5 -2
  24. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  25. package/dist/editor/ui/ItemSearch.js +14 -8
  26. package/dist/editor/ui/ItemSearch.js.map +1 -1
  27. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  28. package/dist/editor/ui/PerfectTree.js +78 -4
  29. package/dist/editor/ui/PerfectTree.js.map +1 -1
  30. package/dist/editor/ui/Splitter.js +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/styles.css +8 -2
  34. package/eslint.config.mjs +4 -0
  35. package/images/bg-shape-black.webp +0 -0
  36. package/images/wizard-bg.png +0 -0
  37. package/images/wizard-tour.png +0 -0
  38. package/images/wizard.png +0 -0
  39. package/package.json +2 -8
  40. package/src/client-components/api.ts +6 -0
  41. package/src/client-components/index.ts +19 -0
  42. package/src/components/ActionButton.tsx +50 -0
  43. package/src/components/Error.tsx +57 -0
  44. package/src/components/ui/CardConnector.tsx +56 -0
  45. package/src/components/ui/button.tsx +62 -0
  46. package/src/components/ui/card.tsx +372 -0
  47. package/src/components/ui/context-menu.tsx +250 -0
  48. package/src/config/config.tsx +917 -0
  49. package/src/config/types.ts +286 -0
  50. package/src/editor/ComponentInfo.tsx +90 -0
  51. package/src/editor/ConfirmationDialog.tsx +103 -0
  52. package/src/editor/ContentTree.tsx +733 -0
  53. package/src/editor/ContextMenu.tsx +230 -0
  54. package/src/editor/Editor.tsx +90 -0
  55. package/src/editor/EditorWarning.tsx +34 -0
  56. package/src/editor/EditorWarnings.tsx +33 -0
  57. package/src/editor/FieldActionsOverlay.tsx +296 -0
  58. package/src/editor/FieldEditorPopup.tsx +65 -0
  59. package/src/editor/FieldHistory.tsx +75 -0
  60. package/src/editor/FieldList.tsx +190 -0
  61. package/src/editor/FieldListField.tsx +391 -0
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
  63. package/src/editor/FloatingToolbar.tsx +163 -0
  64. package/src/editor/ImageEditor.tsx +128 -0
  65. package/src/editor/ItemInfo.tsx +90 -0
  66. package/src/editor/LinkEditorDialog.tsx +196 -0
  67. package/src/editor/MainLayout.tsx +95 -0
  68. package/src/editor/MobileLayout.tsx +68 -0
  69. package/src/editor/NewEditorClient.tsx +11 -0
  70. package/src/editor/PictureCropper.tsx +568 -0
  71. package/src/editor/PictureEditor.tsx +301 -0
  72. package/src/editor/PictureEditorDialog.tsx +381 -0
  73. package/src/editor/PublishDialog.ignore +74 -0
  74. package/src/editor/ScrollingContentTree.tsx +68 -0
  75. package/src/editor/Terminal.tsx +227 -0
  76. package/src/editor/Titlebar.tsx +104 -0
  77. package/src/editor/ai/AiPopup.tsx +59 -0
  78. package/src/editor/ai/AiResponseMessage.tsx +106 -0
  79. package/src/editor/ai/AiTerminal.tsx +503 -0
  80. package/src/editor/ai/AiToolCall.tsx +61 -0
  81. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  82. package/src/editor/ai/GhostWriter.tsx +480 -0
  83. package/src/editor/ai/aiPageModel.ts +108 -0
  84. package/src/editor/ai/editorAiContext.ts +18 -0
  85. package/src/editor/client/AboutDialog.tsx +44 -0
  86. package/src/editor/client/EditorClient.tsx +2241 -0
  87. package/src/editor/client/GenericDialog.tsx +50 -0
  88. package/src/editor/client/editContext.ts +416 -0
  89. package/src/editor/client/helpers.ts +44 -0
  90. package/src/editor/client/itemsRepository.ts +574 -0
  91. package/src/editor/client/operations.ts +768 -0
  92. package/src/editor/client/pageModelBuilder.ts +219 -0
  93. package/src/editor/commands/commands.ts +22 -0
  94. package/src/editor/commands/componentCommands.tsx +431 -0
  95. package/src/editor/commands/createVersionCommand.ts +33 -0
  96. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  97. package/src/editor/commands/itemCommands.tsx +351 -0
  98. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  99. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
  100. package/src/editor/commands/undo.ts +39 -0
  101. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  102. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  103. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  104. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  105. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  106. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  107. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  108. package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
  109. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  110. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  111. package/src/editor/component-designer/aiContext.ts +23 -0
  112. package/src/editor/componentTreeHelper.tsx +116 -0
  113. package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
  114. package/src/editor/context-menu/InsertMenu.tsx +347 -0
  115. package/src/editor/control-center/About.tsx +342 -0
  116. package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
  117. package/src/editor/control-center/IndexOverview.tsx +50 -0
  118. package/src/editor/control-center/IndexSettings.tsx +266 -0
  119. package/src/editor/control-center/Info.tsx +104 -0
  120. package/src/editor/control-center/QuotaInfo.tsx +301 -0
  121. package/src/editor/control-center/Status.tsx +113 -0
  122. package/src/editor/control-center/WebSocketMessages.tsx +155 -0
  123. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  124. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  125. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  126. package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
  127. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  128. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  129. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  130. package/src/editor/field-types/DropLinkEditor.tsx +80 -0
  131. package/src/editor/field-types/DropListEditor.tsx +84 -0
  132. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  133. package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
  134. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  135. package/src/editor/field-types/MultiLineText.tsx +82 -0
  136. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  137. package/src/editor/field-types/RawEditor.tsx +53 -0
  138. package/src/editor/field-types/ReactQuill.tsx +580 -0
  139. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  140. package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
  141. package/src/editor/field-types/SingleLineText.tsx +174 -0
  142. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  143. package/src/editor/fieldTypes.ts +140 -0
  144. package/src/editor/media-selector/AiImageSearch.tsx +185 -0
  145. package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
  146. package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
  147. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  148. package/src/editor/media-selector/Preview.tsx +14 -0
  149. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  150. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  151. package/src/editor/media-selector/UploadZone.tsx +137 -0
  152. package/src/editor/media-selector/index.ts +8 -0
  153. package/src/editor/menubar/ActionsMenu.tsx +94 -0
  154. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  155. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  156. package/src/editor/menubar/BrowseHistory.tsx +28 -0
  157. package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
  158. package/src/editor/menubar/LanguageSelector.tsx +226 -0
  159. package/src/editor/menubar/Menu.tsx +83 -0
  160. package/src/editor/menubar/NavButtons.tsx +74 -0
  161. package/src/editor/menubar/PageSelector.tsx +278 -0
  162. package/src/editor/menubar/PageViewerControls.tsx +120 -0
  163. package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
  164. package/src/editor/menubar/SecondaryControls.tsx +45 -0
  165. package/src/editor/menubar/Separator.tsx +12 -0
  166. package/src/editor/menubar/SiteInfo.tsx +53 -0
  167. package/src/editor/menubar/User.tsx +27 -0
  168. package/src/editor/menubar/VersionSelector.tsx +142 -0
  169. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
  170. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  171. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
  172. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  173. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  174. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  175. package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
  176. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  177. package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
  178. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  179. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  180. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
  181. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
  182. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
  183. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
  184. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
  185. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
  186. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
  187. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  188. package/src/editor/page-viewer/EditorForm.tsx +262 -0
  189. package/src/editor/page-viewer/MiniMap.tsx +362 -0
  190. package/src/editor/page-viewer/PageViewer.tsx +169 -0
  191. package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
  192. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
  193. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  194. package/src/editor/pageModel.ts +220 -0
  195. package/src/editor/picture-shared.tsx +53 -0
  196. package/src/editor/reviews/Comment.tsx +308 -0
  197. package/src/editor/reviews/Comments.tsx +125 -0
  198. package/src/editor/reviews/DiffView.tsx +109 -0
  199. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  200. package/src/editor/reviews/Reviews.tsx +280 -0
  201. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  202. package/src/editor/reviews/reviewCommands.tsx +47 -0
  203. package/src/editor/reviews/useReviews.tsx +70 -0
  204. package/src/editor/services/aiService.ts +173 -0
  205. package/src/editor/services/componentDesignerService.ts +151 -0
  206. package/src/editor/services/contentService.ts +180 -0
  207. package/src/editor/services/editService.ts +488 -0
  208. package/src/editor/services/indexService.ts +24 -0
  209. package/src/editor/services/reviewsService.ts +53 -0
  210. package/src/editor/services/serviceHelper.ts +95 -0
  211. package/src/editor/services/suggestedEditsService.ts +39 -0
  212. package/src/editor/services/systemService.ts +5 -0
  213. package/src/editor/services/translationService.ts +21 -0
  214. package/src/editor/services-server/api.ts +150 -0
  215. package/src/editor/services-server/graphQL.ts +106 -0
  216. package/src/editor/sidebar/ComponentPalette.tsx +161 -0
  217. package/src/editor/sidebar/ComponentTree.tsx +549 -0
  218. package/src/editor/sidebar/Debug.tsx +111 -0
  219. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  220. package/src/editor/sidebar/EditHistory.tsx +134 -0
  221. package/src/editor/sidebar/GraphQL.tsx +164 -0
  222. package/src/editor/sidebar/Insert.tsx +35 -0
  223. package/src/editor/sidebar/MainContentTree.tsx +102 -0
  224. package/src/editor/sidebar/Performance.tsx +53 -0
  225. package/src/editor/sidebar/Sessions.tsx +35 -0
  226. package/src/editor/sidebar/Sidebar.tsx +20 -0
  227. package/src/editor/sidebar/SidebarView.tsx +152 -0
  228. package/src/editor/sidebar/Translations.tsx +295 -0
  229. package/src/editor/sidebar/Validation.tsx +102 -0
  230. package/src/editor/sidebar/ViewSelector.tsx +60 -0
  231. package/src/editor/sidebar/Workbox.tsx +209 -0
  232. package/src/editor/ui/CenteredMessage.tsx +7 -0
  233. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
  234. package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
  235. package/src/editor/ui/DialogButtons.tsx +11 -0
  236. package/src/editor/ui/Icons.tsx +709 -0
  237. package/src/editor/ui/ItemList.tsx +76 -0
  238. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  239. package/src/editor/ui/ItemSearch.tsx +159 -0
  240. package/src/editor/ui/PerfectTree.tsx +676 -0
  241. package/src/editor/ui/Section.tsx +35 -0
  242. package/src/editor/ui/SimpleIconButton.tsx +54 -0
  243. package/src/editor/ui/SimpleMenu.tsx +40 -0
  244. package/src/editor/ui/SimpleTable.tsx +60 -0
  245. package/src/editor/ui/SimpleTabs.tsx +60 -0
  246. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  247. package/src/editor/ui/Spinner.tsx +9 -0
  248. package/src/editor/ui/Splitter.tsx +420 -0
  249. package/src/editor/ui/StackedPanels.tsx +134 -0
  250. package/src/editor/ui/Toolbar.tsx +7 -0
  251. package/src/editor/utils/id-helper.ts +3 -0
  252. package/src/editor/utils/insertOptions.ts +69 -0
  253. package/src/editor/utils/itemutils.ts +29 -0
  254. package/src/editor/utils/useMemoDebug.ts +28 -0
  255. package/src/editor/utils.ts +486 -0
  256. package/src/editor/views/CompareView.tsx +245 -0
  257. package/src/editor/views/EditView.tsx +27 -0
  258. package/src/editor/views/ItemEditor.tsx +58 -0
  259. package/src/editor/views/MediaFolderEditView.tsx +66 -0
  260. package/src/editor/views/SingleEditView.tsx +57 -0
  261. package/src/fonts/Geist-Black.woff2 +0 -0
  262. package/src/fonts/Geist-Bold.woff2 +0 -0
  263. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  264. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  265. package/src/fonts/Geist-Light.woff2 +0 -0
  266. package/src/fonts/Geist-Medium.woff2 +0 -0
  267. package/src/fonts/Geist-Regular.woff2 +0 -0
  268. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  269. package/src/fonts/Geist-Thin.woff2 +0 -0
  270. package/src/fonts/Geist[wght].woff2 +0 -0
  271. package/src/fonts/index.ts +10 -0
  272. package/src/index.ts +23 -0
  273. package/src/lib/safelist.tsx +16 -0
  274. package/src/lib/utils.ts +6 -0
  275. package/src/page-wizard/PageWizard.tsx +139 -0
  276. package/src/page-wizard/WizardBox.tsx +4 -0
  277. package/src/page-wizard/WizardBoxConnector.tsx +56 -0
  278. package/src/page-wizard/WizardSteps.tsx +458 -0
  279. package/src/page-wizard/service.ts +35 -0
  280. package/src/page-wizard/startPageWizardCommand.ts +26 -0
  281. package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
  282. package/src/page-wizard/steps/CollectStep.tsx +296 -0
  283. package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
  284. package/src/page-wizard/steps/Components.tsx +193 -0
  285. package/src/page-wizard/steps/ContentStep.tsx +890 -0
  286. package/src/page-wizard/steps/EditButton.tsx +34 -0
  287. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  288. package/src/page-wizard/steps/Generate.tsx +60 -0
  289. package/src/page-wizard/steps/ImagesStep.tsx +382 -0
  290. package/src/page-wizard/steps/LayoutStep.tsx +227 -0
  291. package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
  292. package/src/page-wizard/steps/SelectStep.tsx +281 -0
  293. package/src/page-wizard/steps/schema.ts +180 -0
  294. package/src/page-wizard/steps/usePageCreator.ts +325 -0
  295. package/src/page-wizard/usePageWizard.ts +79 -0
  296. package/src/revision.ts +2 -0
  297. package/src/splash-screen/NewPage.tsx +294 -0
  298. package/src/splash-screen/OpenPage.tsx +113 -0
  299. package/src/splash-screen/RecentPages.tsx +123 -0
  300. package/src/splash-screen/SectionHeadline.tsx +21 -0
  301. package/src/splash-screen/SplashScreen.tsx +195 -0
  302. package/src/tour/Tour.tsx +566 -0
  303. package/src/tour/default-tour.tsx +301 -0
  304. package/src/tour/preview-tour.tsx +128 -0
  305. package/src/types.ts +335 -0
  306. package/styles.css +765 -0
  307. package/tsconfig.build.json +31 -0
  308. package/tsconfig.json +14 -0
@@ -0,0 +1,301 @@
1
+ import React from "react";
2
+ import { useEditContext } from "../client/editContext";
3
+
4
+ export function QuotaInfo() {
5
+ const editContext = useEditContext();
6
+ const quotaInfo = editContext?.quotaInfo;
7
+
8
+ const formatQuotaPercentage = (used: number, limit: number) => {
9
+ if (!limit || limit === 0 || limit === -1) return "No limit";
10
+ if (!used && used !== 0) return "0%";
11
+ if (isNaN(used) || isNaN(limit)) return "0%";
12
+ const percentage = Math.round((used / limit) * 100);
13
+ return `${percentage}%`;
14
+ };
15
+
16
+ const getQuotaStatus = (used: number, limit: number) => {
17
+ if (!limit || limit === 0 || limit === -1) return "unlimited";
18
+ if (!used && used !== 0) return "ok";
19
+ if (isNaN(used) || isNaN(limit)) return "ok";
20
+ const percentage = (used / limit) * 100;
21
+ if (percentage >= 100) return "exceeded";
22
+ if (percentage >= 90) return "warning";
23
+ if (percentage >= 75) return "caution";
24
+ return "ok";
25
+ };
26
+
27
+ const getProgressWidth = (used: number, limit: number) => {
28
+ if (
29
+ !limit ||
30
+ limit === 0 ||
31
+ limit === -1 ||
32
+ !used ||
33
+ isNaN(used) ||
34
+ isNaN(limit)
35
+ )
36
+ return 0;
37
+ return Math.min(100, (used / limit) * 100);
38
+ };
39
+
40
+ if (!quotaInfo) {
41
+ return (
42
+ <div className="space-y-6 p-4">
43
+ <div className="rounded-lg border border-gray-200 bg-white p-4">
44
+ <h2 className="mb-3 text-xl font-semibold text-gray-800">
45
+ AI Usage Quota
46
+ </h2>
47
+ <p className="text-gray-600">No quota information available</p>
48
+ </div>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <div className="space-y-6 p-4">
55
+ {/* Token Usage Box */}
56
+ <div className="rounded-lg border border-gray-200 bg-white p-4">
57
+ <h3 className="mb-3 text-lg font-semibold text-gray-800">
58
+ Token Usage
59
+ </h3>
60
+ <div className="space-y-4">
61
+ {/* Total Token Usage */}
62
+ <div>
63
+ <div className="mb-2 flex items-center justify-between">
64
+ <label className="text-sm font-medium text-gray-500">
65
+ Total Usage
66
+ </label>
67
+ <span className="text-sm text-gray-600">
68
+ {quotaInfo.usage.totalTokens.toLocaleString()} /{" "}
69
+ {quotaInfo.limits.totalTokens &&
70
+ quotaInfo.limits.totalTokens > 0
71
+ ? quotaInfo.limits.totalTokens.toLocaleString()
72
+ : "∞"}
73
+ </span>
74
+ </div>
75
+ {quotaInfo.limits.totalTokens &&
76
+ quotaInfo.limits.totalTokens > 0 && (
77
+ <div className="h-2 w-full rounded-full bg-gray-200">
78
+ <div
79
+ className={`h-2 rounded-full ${
80
+ getQuotaStatus(
81
+ quotaInfo.usage.totalTokens,
82
+ quotaInfo.limits.totalTokens,
83
+ ) === "exceeded"
84
+ ? "bg-red-500"
85
+ : getQuotaStatus(
86
+ quotaInfo.usage.totalTokens,
87
+ quotaInfo.limits.totalTokens,
88
+ ) === "warning"
89
+ ? "bg-orange-500"
90
+ : getQuotaStatus(
91
+ quotaInfo.usage.totalTokens,
92
+ quotaInfo.limits.totalTokens,
93
+ ) === "caution"
94
+ ? "bg-yellow-500"
95
+ : "bg-green-500"
96
+ }`}
97
+ style={{
98
+ width: `${getProgressWidth(quotaInfo.usage.totalTokens, quotaInfo.limits.totalTokens)}%`,
99
+ }}
100
+ ></div>
101
+ </div>
102
+ )}
103
+ <p className="mt-1 text-xs text-gray-500">
104
+ {formatQuotaPercentage(
105
+ quotaInfo.usage.totalTokens,
106
+ quotaInfo.limits.totalTokens,
107
+ )}{" "}
108
+ used
109
+ </p>
110
+ </div>
111
+
112
+ {/* Daily Token Usage */}
113
+ <div>
114
+ <div className="mb-2 flex items-center justify-between">
115
+ <label className="text-sm font-medium text-gray-500">
116
+ Daily Usage
117
+ </label>
118
+ <span className="text-sm text-gray-600">
119
+ {quotaInfo.usage.dailyTokens.toLocaleString()} /{" "}
120
+ {quotaInfo.limits.dailyTokens &&
121
+ quotaInfo.limits.dailyTokens > 0
122
+ ? quotaInfo.limits.dailyTokens.toLocaleString()
123
+ : "∞"}
124
+ </span>
125
+ </div>
126
+ {quotaInfo.limits.dailyTokens &&
127
+ quotaInfo.limits.dailyTokens > 0 && (
128
+ <div className="h-2 w-full rounded-full bg-gray-200">
129
+ <div
130
+ className={`h-2 rounded-full ${
131
+ getQuotaStatus(
132
+ quotaInfo.usage.dailyTokens,
133
+ quotaInfo.limits.dailyTokens,
134
+ ) === "exceeded"
135
+ ? "bg-red-500"
136
+ : getQuotaStatus(
137
+ quotaInfo.usage.dailyTokens,
138
+ quotaInfo.limits.dailyTokens,
139
+ ) === "warning"
140
+ ? "bg-orange-500"
141
+ : getQuotaStatus(
142
+ quotaInfo.usage.dailyTokens,
143
+ quotaInfo.limits.dailyTokens,
144
+ ) === "caution"
145
+ ? "bg-yellow-500"
146
+ : "bg-green-500"
147
+ }`}
148
+ style={{
149
+ width: `${getProgressWidth(quotaInfo.usage.dailyTokens, quotaInfo.limits.dailyTokens)}%`,
150
+ }}
151
+ ></div>
152
+ </div>
153
+ )}
154
+ <p className="mt-1 text-xs text-gray-500">
155
+ {quotaInfo.limits.dailyTokens === -1
156
+ ? "No daily limit"
157
+ : `${formatQuotaPercentage(
158
+ quotaInfo.usage.dailyTokens,
159
+ quotaInfo.limits.dailyTokens,
160
+ )} used today`}
161
+ </p>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ {/* Image Usage Box */}
167
+ <div className="rounded-lg border border-gray-200 bg-white p-4">
168
+ <h3 className="mb-3 text-lg font-semibold text-gray-800">
169
+ Image Usage
170
+ </h3>
171
+ <div className="space-y-4">
172
+ {/* Total Image Usage */}
173
+ <div>
174
+ <div className="mb-2 flex items-center justify-between">
175
+ <label className="text-sm font-medium text-gray-500">
176
+ Total Usage
177
+ </label>
178
+ <span className="text-sm text-gray-600">
179
+ {quotaInfo.usage.totalImages.toLocaleString()} /{" "}
180
+ {quotaInfo.limits.totalImages &&
181
+ quotaInfo.limits.totalImages > 0
182
+ ? quotaInfo.limits.totalImages.toLocaleString()
183
+ : "∞"}
184
+ </span>
185
+ </div>
186
+ {quotaInfo.limits.totalImages &&
187
+ quotaInfo.limits.totalImages > 0 && (
188
+ <div className="h-2 w-full rounded-full bg-gray-200">
189
+ <div
190
+ className={`h-2 rounded-full ${
191
+ getQuotaStatus(
192
+ quotaInfo.usage.totalImages,
193
+ quotaInfo.limits.totalImages,
194
+ ) === "exceeded"
195
+ ? "bg-red-500"
196
+ : getQuotaStatus(
197
+ quotaInfo.usage.totalImages,
198
+ quotaInfo.limits.totalImages,
199
+ ) === "warning"
200
+ ? "bg-orange-500"
201
+ : getQuotaStatus(
202
+ quotaInfo.usage.totalImages,
203
+ quotaInfo.limits.totalImages,
204
+ ) === "caution"
205
+ ? "bg-yellow-500"
206
+ : "bg-green-500"
207
+ }`}
208
+ style={{
209
+ width: `${getProgressWidth(quotaInfo.usage.totalImages, quotaInfo.limits.totalImages)}%`,
210
+ }}
211
+ ></div>
212
+ </div>
213
+ )}
214
+ <p className="mt-1 text-xs text-gray-500">
215
+ {formatQuotaPercentage(
216
+ quotaInfo.usage.totalImages,
217
+ quotaInfo.limits.totalImages,
218
+ )}{" "}
219
+ used
220
+ </p>
221
+ </div>
222
+
223
+ {/* Daily Image Usage */}
224
+ <div>
225
+ <div className="mb-2 flex items-center justify-between">
226
+ <label className="text-sm font-medium text-gray-500">
227
+ Daily Usage
228
+ </label>
229
+ <span className="text-sm text-gray-600">
230
+ {quotaInfo.usage.dailyImages.toLocaleString()} /{" "}
231
+ {quotaInfo.limits.dailyImages &&
232
+ quotaInfo.limits.dailyImages > 0
233
+ ? quotaInfo.limits.dailyImages.toLocaleString()
234
+ : "∞"}
235
+ </span>
236
+ </div>
237
+ {quotaInfo.limits.dailyImages &&
238
+ quotaInfo.limits.dailyImages > 0 && (
239
+ <div className="h-2 w-full rounded-full bg-gray-200">
240
+ <div
241
+ className={`h-2 rounded-full ${
242
+ getQuotaStatus(
243
+ quotaInfo.usage.dailyImages,
244
+ quotaInfo.limits.dailyImages,
245
+ ) === "exceeded"
246
+ ? "bg-red-500"
247
+ : getQuotaStatus(
248
+ quotaInfo.usage.dailyImages,
249
+ quotaInfo.limits.dailyImages,
250
+ ) === "warning"
251
+ ? "bg-orange-500"
252
+ : getQuotaStatus(
253
+ quotaInfo.usage.dailyImages,
254
+ quotaInfo.limits.dailyImages,
255
+ ) === "caution"
256
+ ? "bg-yellow-500"
257
+ : "bg-green-500"
258
+ }`}
259
+ style={{
260
+ width: `${getProgressWidth(quotaInfo.usage.dailyImages, quotaInfo.limits.dailyImages)}%`,
261
+ }}
262
+ ></div>
263
+ </div>
264
+ )}
265
+ <p className="mt-1 text-xs text-gray-500">
266
+ {quotaInfo.limits.dailyImages === -1
267
+ ? "No daily limit"
268
+ : `${formatQuotaPercentage(
269
+ quotaInfo.usage.dailyImages,
270
+ quotaInfo.limits.dailyImages,
271
+ )} used today`}
272
+ </p>
273
+ </div>
274
+ </div>
275
+ </div>
276
+
277
+ {/* Quota Status Summary */}
278
+ <div className="rounded-lg border border-gray-200 bg-white p-4">
279
+ <h4 className="mb-2 text-sm font-medium text-gray-700">Quota Status</h4>
280
+ <div className="space-y-1 text-sm">
281
+ {editContext?.isQuotaExceeded && (
282
+ <p className="font-medium text-red-600">
283
+ ⚠️ Quota limits have been exceeded
284
+ </p>
285
+ )}
286
+ {editContext?.getQuotaWarningMessage &&
287
+ editContext.getQuotaWarningMessage() &&
288
+ !editContext.isQuotaExceeded && (
289
+ <p className="text-orange-600">
290
+ ⚠️ {editContext.getQuotaWarningMessage()}
291
+ </p>
292
+ )}
293
+ {!editContext?.isQuotaExceeded &&
294
+ !editContext?.getQuotaWarningMessage?.() && (
295
+ <p className="text-green-600">✅ All quotas are within limits</p>
296
+ )}
297
+ </div>
298
+ </div>
299
+ </div>
300
+ );
301
+ }
@@ -0,0 +1,113 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useRouter } from "next/navigation";
3
+ import { usePathname, useSearchParams } from "next/navigation";
4
+ import { useEditContext } from "../client/editContext";
5
+ import { SimpleMenu } from "../ui/SimpleMenu";
6
+ import { Splitter, SplitterPanel } from "../ui/Splitter";
7
+
8
+ export function Status() {
9
+ const editContext = useEditContext();
10
+ const config = editContext?.configuration;
11
+ const searchParams = useSearchParams();
12
+ const urlActiveItemKey = searchParams.get("ccpanel");
13
+
14
+ // Get the first available panel as default
15
+ const defaultActiveItemKey = config?.controlCenter.groups?.flatMap(
16
+ (x) => x.panels,
17
+ )?.[0]?.id;
18
+
19
+ const [activeItemKey, setActiveItemKey] = useState<string | null>(
20
+ urlActiveItemKey || defaultActiveItemKey || null,
21
+ );
22
+
23
+ const router = useRouter();
24
+ const pathname = usePathname();
25
+
26
+ const updateUrl = (key: string | null) => {
27
+ if (urlActiveItemKey === key) return;
28
+
29
+ const current = new URLSearchParams(Array.from(searchParams.entries()));
30
+
31
+ if (key) {
32
+ current.set("ccpanel", key);
33
+ } else {
34
+ current.delete("ccpanel");
35
+ }
36
+ router.push(`${pathname}?${current.toString()}`, { scroll: false });
37
+ };
38
+
39
+ // Set default active item when config loads and no active item is set
40
+ useEffect(() => {
41
+ if (!activeItemKey && defaultActiveItemKey) {
42
+ setActiveItemKey(defaultActiveItemKey);
43
+ }
44
+ }, [defaultActiveItemKey, activeItemKey]);
45
+
46
+ const items = config?.controlCenter.groups.map((group) => {
47
+ return {
48
+ id: group.title,
49
+ label: group.title,
50
+ icon: group.icon,
51
+ items:
52
+ group.panels.map((panel) => ({
53
+ id: panel.id,
54
+ label: panel.title,
55
+ })) || [],
56
+ };
57
+ });
58
+
59
+ // Find the currently selected panel content
60
+ const selectedPanel = config?.controlCenter.groups
61
+ ?.flatMap((x) => x.panels)
62
+ ?.find((item) => item.id === activeItemKey);
63
+
64
+ if (!items) {
65
+ return (
66
+ <div className="flex h-full flex-col items-center justify-center">
67
+ Loading...
68
+ </div>
69
+ );
70
+ }
71
+
72
+ const panels: SplitterPanel[] = [
73
+ {
74
+ name: "menu",
75
+ defaultSize: 300,
76
+ content: (
77
+ <div className="h-full border-r border-gray-200">
78
+ <SimpleMenu
79
+ items={items}
80
+ activeItemKey={activeItemKey}
81
+ onItemClick={(item) => {
82
+ setActiveItemKey(item.id);
83
+ // Only update URL when user explicitly clicks on an item
84
+ updateUrl(item.id);
85
+ }}
86
+ />
87
+ </div>
88
+ ),
89
+ },
90
+ {
91
+ name: "content",
92
+ defaultSize: "auto",
93
+ content: (
94
+ <div className="absolute inset-0 overflow-auto">
95
+ {selectedPanel ? (
96
+ selectedPanel.content
97
+ ) : (
98
+ <div className="flex h-full flex-col items-center justify-center text-gray-500">
99
+ <i className="pi pi-info-circle mb-4 text-4xl"></i>
100
+ <p>Select a panel from the menu to view its content</p>
101
+ </div>
102
+ )}
103
+ </div>
104
+ ),
105
+ },
106
+ ];
107
+
108
+ return (
109
+ <div className="h-full">
110
+ <Splitter panels={panels} localStorageKey="control-center-splitter" />
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,155 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { useEditContext } from "../client/editContext";
3
+ import { WebSocketMessage } from "../client/EditorClient";
4
+
5
+ export function WebSocketMessages() {
6
+ const editContext = useEditContext();
7
+ const [expandedMessages, setExpandedMessages] = useState<Set<string>>(
8
+ new Set(),
9
+ );
10
+ const [autoScroll, setAutoScroll] = useState(true);
11
+ const messagesEndRef = useRef<HTMLDivElement>(null);
12
+ const containerRef = useRef<HTMLDivElement>(null);
13
+
14
+ // Get messages from central store
15
+ const messages = editContext?.webSocketMessages || [];
16
+
17
+ // Auto-scroll to bottom when new messages arrive
18
+ useEffect(() => {
19
+ if (autoScroll && messagesEndRef.current) {
20
+ messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
21
+ }
22
+ }, [messages, autoScroll]);
23
+
24
+ const toggleExpanded = (messageId: string) => {
25
+ setExpandedMessages((prev) => {
26
+ const newSet = new Set(prev);
27
+ if (newSet.has(messageId)) {
28
+ newSet.delete(messageId);
29
+ } else {
30
+ newSet.add(messageId);
31
+ }
32
+ return newSet;
33
+ });
34
+ };
35
+
36
+ const formatTimestamp = (isoString: string) => {
37
+ const date = new Date(isoString);
38
+ return (
39
+ date.toLocaleTimeString() +
40
+ "." +
41
+ date.getMilliseconds().toString().padStart(3, "0")
42
+ );
43
+ };
44
+
45
+ const getMessageTypeColor = (type: string) => {
46
+ const colors: { [key: string]: string } = {
47
+ "active-sessions": "bg-blue-100 text-blue-800",
48
+ "item-deleted": "bg-red-100 text-red-800",
49
+ "item-changed": "bg-yellow-100 text-yellow-800",
50
+ "item-version-added": "bg-green-100 text-green-800",
51
+ "edit-operation": "bg-purple-100 text-purple-800",
52
+ "executing-field-action": "bg-orange-100 text-orange-800",
53
+ "comment-updated": "bg-cyan-100 text-cyan-800",
54
+ "comment-deleted": "bg-red-100 text-red-800",
55
+ "suggested-edit-updated": "bg-indigo-100 text-indigo-800",
56
+ "suggested-edit-deleted": "bg-red-100 text-red-800",
57
+ "update-quota": "bg-gray-100 text-gray-800",
58
+ };
59
+ return colors[type] || "bg-gray-100 text-gray-800";
60
+ };
61
+
62
+ const clearMessages = () => {
63
+ editContext?.clearWebSocketMessages();
64
+ };
65
+
66
+ const handleScroll = () => {
67
+ if (!containerRef.current) return;
68
+
69
+ const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
70
+ const isAtBottom = scrollHeight - scrollTop <= clientHeight + 10;
71
+ setAutoScroll(isAtBottom);
72
+ };
73
+
74
+ return (
75
+ <div className="flex h-full flex-col">
76
+ {/* Header */}
77
+ <div className="flex items-center justify-between border-b border-gray-200 p-4">
78
+ <div className="flex items-center gap-2">
79
+ <i className="pi pi-comments text-lg" />
80
+ <h3 className="text-lg font-semibold">WebSocket Messages</h3>
81
+ <span className="rounded bg-gray-100 px-2 py-1 text-sm text-gray-800">
82
+ {messages.length}
83
+ </span>
84
+ </div>
85
+ <div className="flex items-center gap-2">
86
+ <label className="flex items-center gap-1 text-sm">
87
+ <input
88
+ type="checkbox"
89
+ checked={autoScroll}
90
+ onChange={(e) => setAutoScroll(e.target.checked)}
91
+ className="rounded"
92
+ />
93
+ Auto-scroll
94
+ </label>
95
+ <button
96
+ onClick={clearMessages}
97
+ className="rounded bg-red-500 px-3 py-1 text-sm text-white hover:bg-red-600"
98
+ >
99
+ Clear
100
+ </button>
101
+ </div>
102
+ </div>
103
+
104
+ {/* Messages List */}
105
+ <div
106
+ ref={containerRef}
107
+ className="flex-1 space-y-2 overflow-y-auto p-4"
108
+ onScroll={handleScroll}
109
+ >
110
+ {messages.length === 0 ? (
111
+ <div className="py-8 text-center text-gray-500">
112
+ No WebSocket messages received yet
113
+ </div>
114
+ ) : (
115
+ messages.map((message) => (
116
+ <div key={message.id} className="rounded-lg border border-gray-200">
117
+ <div
118
+ className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50"
119
+ onClick={() => toggleExpanded(message.id)}
120
+ >
121
+ <div className="flex min-w-0 flex-1 items-center gap-3">
122
+ <span className="font-mono text-xs text-gray-500">
123
+ {formatTimestamp(message.timestamp)}
124
+ </span>
125
+ <span
126
+ className={`rounded px-2 py-1 text-xs font-medium ${getMessageTypeColor(message.type)}`}
127
+ >
128
+ {message.type}
129
+ </span>
130
+ <span className="truncate text-sm text-gray-600">
131
+ {typeof message.payload === "object"
132
+ ? Object.keys(message.payload).join(", ")
133
+ : String(message.payload)}
134
+ </span>
135
+ </div>
136
+ <i
137
+ className={`pi ${expandedMessages.has(message.id) ? "pi-chevron-up" : "pi-chevron-down"} text-gray-400`}
138
+ />
139
+ </div>
140
+
141
+ {expandedMessages.has(message.id) && (
142
+ <div className="border-t border-gray-200 bg-gray-50 p-3">
143
+ <pre className="overflow-x-auto font-mono text-xs break-words whitespace-pre-wrap">
144
+ {message.rawMessage}
145
+ </pre>
146
+ </div>
147
+ )}
148
+ </div>
149
+ ))
150
+ )}
151
+ <div ref={messagesEndRef} />
152
+ </div>
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,63 @@
1
+ import { EditorWarning } from "../EditorWarning";
2
+ import { EditorWarningProps } from "../EditorWarnings";
3
+ import { useEditContext } from "../client/editContext";
4
+ import { ItemDescriptor } from "../pageModel";
5
+
6
+ export function ItemLocked({ item }: EditorWarningProps) {
7
+ if (!item.canLock) return;
8
+ var editContext = useEditContext();
9
+ if (!editContext) return;
10
+ if (item.hasLock) {
11
+ return (
12
+ <EditorWarning title="Item locked" severity="warning">
13
+ <button
14
+ onClick={() => {
15
+ const items: ItemDescriptor[] = [item];
16
+ editContext!.operations.unlockItems(items);
17
+ }}
18
+ >
19
+ Release lock
20
+ </button>
21
+ </EditorWarning>
22
+ );
23
+ }
24
+ if (item.lockedBy == null) {
25
+ return (
26
+ <EditorWarning title="Item is locked" severity="warning">
27
+ <div>Locked by {item.lockedBy}</div>
28
+ {/* {item.canLock && (
29
+ <button
30
+ onClick={() => {
31
+ const items: ItemDescriptor[] = [item];
32
+ if ((item as ComponentData).linkedComponentItem)
33
+ items.push((item as ComponentData).linkedComponentItem);
34
+
35
+ editContext!.lockItems(items);
36
+ }}
37
+ >
38
+ Grab lock
39
+ </button>
40
+ )} */}
41
+ </EditorWarning>
42
+ );
43
+ }
44
+ // else {
45
+ // return (
46
+ // <EditorWarning title="Item not locked" severity="warning">
47
+ // {item.canLock && (
48
+ // <button
49
+ // onClick={() => {
50
+ // const items: ItemDescriptor[] = [item];
51
+ // if ((item as ComponentData).linkedComponentItem)
52
+ // items.push((item as ComponentData).linkedComponentItem);
53
+
54
+ // editContext!.lockItems(items);
55
+ // }}
56
+ // >
57
+ // Lock item
58
+ // </button>
59
+ // )}
60
+ // </EditorWarning>
61
+ // );
62
+ // }
63
+ }
@@ -0,0 +1,22 @@
1
+ import { EditorWarning } from "../EditorWarning";
2
+ import { EditorWarningProps } from "../EditorWarnings";
3
+ import { useEditContext } from "../client/editContext";
4
+
5
+ export function NoWriteLanguageAccess({ item }: EditorWarningProps) {
6
+ var editContext = useEditContext();
7
+ if (!editContext) return;
8
+ if (!item.canWriteItem) return;
9
+ if (!item.canWriteLanguage) {
10
+ return (
11
+ <EditorWarning
12
+ title="You dont have language write access."
13
+ severity="warning"
14
+ >
15
+ <p>
16
+ You can't edit this item because you don't have write access for
17
+ language {editContext.page?.item.language}.
18
+ </p>
19
+ </EditorWarning>
20
+ );
21
+ }
22
+ }
@@ -0,0 +1,23 @@
1
+ import { EditorWarning } from "../EditorWarning";
2
+ import { EditorWarningProps } from "../EditorWarnings";
3
+ import { useEditContext } from "../client/editContext";
4
+
5
+ export function NoWorkflowWriteAccess({ item }: EditorWarningProps) {
6
+ var editContext = useEditContext();
7
+ if (!editContext) return;
8
+ if (!item.canWriteItem || !item.canWriteLanguage) return;
9
+ if (!item.canWriteWorkflow) {
10
+ return (
11
+ <EditorWarning
12
+ title="You dont have write access in the current workflow state."
13
+ severity="warning"
14
+ >
15
+ <p>
16
+ The item is in the workflow state{" "}
17
+ <span className="font-bold">{item.workflowState}</span> which
18
+ prevents you from editing.
19
+ </p>
20
+ </EditorWarning>
21
+ );
22
+ }
23
+ }