@nuasite/cms 0.1.0

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 (269) hide show
  1. package/README.md +237 -0
  2. package/dist/src/build-processor.d.ts +20 -0
  3. package/dist/src/build-processor.d.ts.map +1 -0
  4. package/dist/src/collection-scanner.d.ts +6 -0
  5. package/dist/src/collection-scanner.d.ts.map +1 -0
  6. package/dist/src/component-registry.d.ts +63 -0
  7. package/dist/src/component-registry.d.ts.map +1 -0
  8. package/dist/src/config.d.ts +24 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/dev-middleware.d.ts +20 -0
  11. package/dist/src/dev-middleware.d.ts.map +1 -0
  12. package/dist/src/editor/ai.d.ts +60 -0
  13. package/dist/src/editor/ai.d.ts.map +1 -0
  14. package/dist/src/editor/api.d.ts +140 -0
  15. package/dist/src/editor/api.d.ts.map +1 -0
  16. package/dist/src/editor/color-utils.d.ts +106 -0
  17. package/dist/src/editor/color-utils.d.ts.map +1 -0
  18. package/dist/src/editor/components/ai-chat.d.ts +11 -0
  19. package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
  20. package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
  21. package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
  22. package/dist/src/editor/components/attribute-editor.d.ts +5 -0
  23. package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
  24. package/dist/src/editor/components/block-editor.d.ts +12 -0
  25. package/dist/src/editor/components/block-editor.d.ts.map +1 -0
  26. package/dist/src/editor/components/collections-browser.d.ts +2 -0
  27. package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
  28. package/dist/src/editor/components/color-toolbar.d.ts +12 -0
  29. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
  30. package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
  31. package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
  32. package/dist/src/editor/components/create-page-modal.d.ts +2 -0
  33. package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
  34. package/dist/src/editor/components/editable-highlights.d.ts +9 -0
  35. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
  36. package/dist/src/editor/components/error-boundary.d.ts +32 -0
  37. package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
  38. package/dist/src/editor/components/fields.d.ts +75 -0
  39. package/dist/src/editor/components/fields.d.ts.map +1 -0
  40. package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
  41. package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
  42. package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
  43. package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
  44. package/dist/src/editor/components/image-overlay.d.ts +12 -0
  45. package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
  46. package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
  47. package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
  48. package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
  49. package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
  50. package/dist/src/editor/components/media-library.d.ts +2 -0
  51. package/dist/src/editor/components/media-library.d.ts.map +1 -0
  52. package/dist/src/editor/components/outline.d.ts +21 -0
  53. package/dist/src/editor/components/outline.d.ts.map +1 -0
  54. package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
  55. package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
  56. package/dist/src/editor/components/seo-editor.d.ts +2 -0
  57. package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
  58. package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
  59. package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
  60. package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
  61. package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
  62. package/dist/src/editor/components/toast/toast.d.ts +7 -0
  63. package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
  64. package/dist/src/editor/components/toast/types.d.ts +7 -0
  65. package/dist/src/editor/components/toast/types.d.ts.map +1 -0
  66. package/dist/src/editor/components/toolbar.d.ts +21 -0
  67. package/dist/src/editor/components/toolbar.d.ts.map +1 -0
  68. package/dist/src/editor/config.d.ts +4 -0
  69. package/dist/src/editor/config.d.ts.map +1 -0
  70. package/dist/src/editor/constants.d.ts +101 -0
  71. package/dist/src/editor/constants.d.ts.map +1 -0
  72. package/dist/src/editor/context.d.ts +14 -0
  73. package/dist/src/editor/context.d.ts.map +1 -0
  74. package/dist/src/editor/dom.d.ts +77 -0
  75. package/dist/src/editor/dom.d.ts.map +1 -0
  76. package/dist/src/editor/editor.d.ts +64 -0
  77. package/dist/src/editor/editor.d.ts.map +1 -0
  78. package/dist/src/editor/history.d.ts +20 -0
  79. package/dist/src/editor/history.d.ts.map +1 -0
  80. package/dist/src/editor/hooks/index.d.ts +14 -0
  81. package/dist/src/editor/hooks/index.d.ts.map +1 -0
  82. package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
  83. package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
  84. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
  85. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
  86. package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
  87. package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
  88. package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
  89. package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
  90. package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
  91. package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
  92. package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
  93. package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
  94. package/dist/src/editor/hooks/utils.d.ts +32 -0
  95. package/dist/src/editor/hooks/utils.d.ts.map +1 -0
  96. package/dist/src/editor/index.d.ts +12 -0
  97. package/dist/src/editor/index.d.ts.map +1 -0
  98. package/dist/src/editor/lib/cn.d.ts +3 -0
  99. package/dist/src/editor/lib/cn.d.ts.map +1 -0
  100. package/dist/src/editor/manifest.d.ts +19 -0
  101. package/dist/src/editor/manifest.d.ts.map +1 -0
  102. package/dist/src/editor/markdown-api.d.ts +36 -0
  103. package/dist/src/editor/markdown-api.d.ts.map +1 -0
  104. package/dist/src/editor/signals.d.ts +242 -0
  105. package/dist/src/editor/signals.d.ts.map +1 -0
  106. package/dist/src/editor/storage.d.ts +27 -0
  107. package/dist/src/editor/storage.d.ts.map +1 -0
  108. package/dist/src/editor/text-styling.d.ts +350 -0
  109. package/dist/src/editor/text-styling.d.ts.map +1 -0
  110. package/dist/src/editor/themes.d.ts +38 -0
  111. package/dist/src/editor/themes.d.ts.map +1 -0
  112. package/dist/src/editor/types.d.ts +454 -0
  113. package/dist/src/editor/types.d.ts.map +1 -0
  114. package/dist/src/error-collector.d.ts +56 -0
  115. package/dist/src/error-collector.d.ts.map +1 -0
  116. package/dist/src/handlers/component-ops.d.ts +34 -0
  117. package/dist/src/handlers/component-ops.d.ts.map +1 -0
  118. package/dist/src/handlers/markdown-ops.d.ts +41 -0
  119. package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
  120. package/dist/src/handlers/request-utils.d.ts +20 -0
  121. package/dist/src/handlers/request-utils.d.ts.map +1 -0
  122. package/dist/src/handlers/source-writer.d.ts +51 -0
  123. package/dist/src/handlers/source-writer.d.ts.map +1 -0
  124. package/dist/src/html-processor.d.ts +63 -0
  125. package/dist/src/html-processor.d.ts.map +1 -0
  126. package/dist/src/index.d.ts +41 -0
  127. package/dist/src/index.d.ts.map +1 -0
  128. package/dist/src/manifest-writer.d.ts +111 -0
  129. package/dist/src/manifest-writer.d.ts.map +1 -0
  130. package/dist/src/media/contember.d.ts +15 -0
  131. package/dist/src/media/contember.d.ts.map +1 -0
  132. package/dist/src/media/local.d.ts +9 -0
  133. package/dist/src/media/local.d.ts.map +1 -0
  134. package/dist/src/media/s3.d.ts +12 -0
  135. package/dist/src/media/s3.d.ts.map +1 -0
  136. package/dist/src/media/types.d.ts +40 -0
  137. package/dist/src/media/types.d.ts.map +1 -0
  138. package/dist/src/preview-generator.d.ts +19 -0
  139. package/dist/src/preview-generator.d.ts.map +1 -0
  140. package/dist/src/seo-processor.d.ts +23 -0
  141. package/dist/src/seo-processor.d.ts.map +1 -0
  142. package/dist/src/source-finder/ast-extractors.d.ts +35 -0
  143. package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
  144. package/dist/src/source-finder/ast-parser.d.ts +16 -0
  145. package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
  146. package/dist/src/source-finder/cache.d.ts +18 -0
  147. package/dist/src/source-finder/cache.d.ts.map +1 -0
  148. package/dist/src/source-finder/collection-finder.d.ts +29 -0
  149. package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
  150. package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
  151. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
  152. package/dist/src/source-finder/element-finder.d.ts +42 -0
  153. package/dist/src/source-finder/element-finder.d.ts.map +1 -0
  154. package/dist/src/source-finder/image-finder.d.ts +24 -0
  155. package/dist/src/source-finder/image-finder.d.ts.map +1 -0
  156. package/dist/src/source-finder/index.d.ts +9 -0
  157. package/dist/src/source-finder/index.d.ts.map +1 -0
  158. package/dist/src/source-finder/search-index.d.ts +27 -0
  159. package/dist/src/source-finder/search-index.d.ts.map +1 -0
  160. package/dist/src/source-finder/snippet-utils.d.ts +90 -0
  161. package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
  162. package/dist/src/source-finder/source-lookup.d.ts +16 -0
  163. package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
  164. package/dist/src/source-finder/types.d.ts +167 -0
  165. package/dist/src/source-finder/types.d.ts.map +1 -0
  166. package/dist/src/source-finder/variable-extraction.d.ts +37 -0
  167. package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
  168. package/dist/src/tailwind-colors.d.ts +54 -0
  169. package/dist/src/tailwind-colors.d.ts.map +1 -0
  170. package/dist/src/tsconfig.tsbuildinfo +1 -0
  171. package/dist/src/types.d.ts +367 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/utils.d.ts +61 -0
  174. package/dist/src/utils.d.ts.map +1 -0
  175. package/dist/src/vite-plugin.d.ts +14 -0
  176. package/dist/src/vite-plugin.d.ts.map +1 -0
  177. package/dist/types/tsconfig.tsbuildinfo +1 -0
  178. package/package.json +80 -0
  179. package/src/build-processor.ts +784 -0
  180. package/src/collection-scanner.ts +304 -0
  181. package/src/component-registry.ts +393 -0
  182. package/src/config.ts +74 -0
  183. package/src/dev-middleware.ts +525 -0
  184. package/src/dist/src/tsconfig.tsbuildinfo +1 -0
  185. package/src/editor/ai.ts +185 -0
  186. package/src/editor/api.ts +513 -0
  187. package/src/editor/color-utils.ts +556 -0
  188. package/src/editor/components/ai-chat.tsx +632 -0
  189. package/src/editor/components/ai-tooltip.tsx +179 -0
  190. package/src/editor/components/attribute-editor.tsx +596 -0
  191. package/src/editor/components/block-editor.tsx +546 -0
  192. package/src/editor/components/collections-browser.tsx +248 -0
  193. package/src/editor/components/color-toolbar.tsx +314 -0
  194. package/src/editor/components/confirm-dialog.tsx +69 -0
  195. package/src/editor/components/create-page-modal.tsx +163 -0
  196. package/src/editor/components/editable-highlights.tsx +260 -0
  197. package/src/editor/components/error-boundary.tsx +87 -0
  198. package/src/editor/components/fields.tsx +387 -0
  199. package/src/editor/components/frontmatter-fields.tsx +469 -0
  200. package/src/editor/components/highlight-overlay.ts +229 -0
  201. package/src/editor/components/image-overlay.tsx +230 -0
  202. package/src/editor/components/markdown-editor-overlay.tsx +505 -0
  203. package/src/editor/components/markdown-inline-editor.tsx +780 -0
  204. package/src/editor/components/media-library.tsx +297 -0
  205. package/src/editor/components/outline.tsx +402 -0
  206. package/src/editor/components/redirect-countdown.tsx +45 -0
  207. package/src/editor/components/seo-editor.tsx +498 -0
  208. package/src/editor/components/text-style-toolbar.tsx +362 -0
  209. package/src/editor/components/toast/toast-container.tsx +15 -0
  210. package/src/editor/components/toast/toast.tsx +49 -0
  211. package/src/editor/components/toast/types.ts +7 -0
  212. package/src/editor/components/toolbar.tsx +366 -0
  213. package/src/editor/config.ts +12 -0
  214. package/src/editor/constants.ts +106 -0
  215. package/src/editor/context.tsx +38 -0
  216. package/src/editor/dom.ts +357 -0
  217. package/src/editor/editor.ts +1510 -0
  218. package/src/editor/env.d.ts +4 -0
  219. package/src/editor/history.ts +355 -0
  220. package/src/editor/hooks/index.ts +19 -0
  221. package/src/editor/hooks/useAIHandlers.ts +345 -0
  222. package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
  223. package/src/editor/hooks/useElementDetection.ts +284 -0
  224. package/src/editor/hooks/useImageHoverDetection.ts +102 -0
  225. package/src/editor/hooks/useTextSelection.ts +187 -0
  226. package/src/editor/hooks/useTooltipState.ts +126 -0
  227. package/src/editor/hooks/utils.ts +101 -0
  228. package/src/editor/index.tsx +481 -0
  229. package/src/editor/lib/cn.ts +4 -0
  230. package/src/editor/manifest.ts +25 -0
  231. package/src/editor/markdown-api.ts +209 -0
  232. package/src/editor/signals.ts +1351 -0
  233. package/src/editor/storage.ts +266 -0
  234. package/src/editor/styles.css +465 -0
  235. package/src/editor/text-styling.ts +773 -0
  236. package/src/editor/themes.ts +210 -0
  237. package/src/editor/types.ts +591 -0
  238. package/src/error-collector.ts +106 -0
  239. package/src/handlers/component-ops.ts +463 -0
  240. package/src/handlers/markdown-ops.ts +202 -0
  241. package/src/handlers/request-utils.ts +151 -0
  242. package/src/handlers/source-writer.ts +649 -0
  243. package/src/html-processor.ts +1108 -0
  244. package/src/index.ts +284 -0
  245. package/src/manifest-writer.ts +371 -0
  246. package/src/media/contember.ts +84 -0
  247. package/src/media/local.ts +114 -0
  248. package/src/media/s3.ts +133 -0
  249. package/src/media/types.ts +33 -0
  250. package/src/preview-generator.ts +293 -0
  251. package/src/seo-processor.ts +567 -0
  252. package/src/source-finder/ast-extractors.ts +185 -0
  253. package/src/source-finder/ast-parser.ts +150 -0
  254. package/src/source-finder/cache.ts +76 -0
  255. package/src/source-finder/collection-finder.ts +335 -0
  256. package/src/source-finder/cross-file-tracker.ts +741 -0
  257. package/src/source-finder/element-finder.ts +387 -0
  258. package/src/source-finder/image-finder.ts +283 -0
  259. package/src/source-finder/index.ts +37 -0
  260. package/src/source-finder/search-index.ts +525 -0
  261. package/src/source-finder/snippet-utils.ts +668 -0
  262. package/src/source-finder/source-lookup.ts +200 -0
  263. package/src/source-finder/types.ts +210 -0
  264. package/src/source-finder/variable-extraction.ts +406 -0
  265. package/src/tailwind-colors.ts +874 -0
  266. package/src/tsconfig.json +25 -0
  267. package/src/types.ts +406 -0
  268. package/src/utils.ts +186 -0
  269. package/src/vite-plugin.ts +42 -0
@@ -0,0 +1,513 @@
1
+ import { API } from './constants'
2
+ import { setAvailableTextStyles } from './text-styling'
3
+ import type {
4
+ CmsManifest,
5
+ ComponentInsertOperation,
6
+ DeploymentStatusResponse,
7
+ SaveBatchRequest,
8
+ SaveBatchResponse,
9
+ UpdateMarkdownPageRequest,
10
+ UpdateMarkdownPageResponse,
11
+ } from './types'
12
+
13
+ /**
14
+ * Response from fetching markdown content
15
+ */
16
+ export interface GetMarkdownContentResponse {
17
+ content: string
18
+ frontmatter: Record<string, unknown>
19
+ filePath: string
20
+ }
21
+
22
+ /**
23
+ * Types for CMS AI chat communication (SSE events)
24
+ */
25
+ export interface CmsAiChatRequest {
26
+ prompt: string
27
+ elementId?: string
28
+ currentContent?: string
29
+ pageUrl: string
30
+ /** Source file context for the element being edited */
31
+ context?: string
32
+ sessionId?: string
33
+ }
34
+
35
+ export type CmsAiEvent =
36
+ | CmsAiTokenEvent
37
+ | CmsAiStatusEvent
38
+ | CmsAiActionEvent
39
+ | CmsAiErrorEvent
40
+ | CmsAiDoneEvent
41
+
42
+ export interface CmsAiTokenEvent {
43
+ type: 'token'
44
+ token: string
45
+ fullText: string
46
+ }
47
+
48
+ export interface CmsAiStatusEvent {
49
+ type: 'status'
50
+ status: 'thinking' | 'coding' | 'building' | 'deploying' | 'complete'
51
+ message?: string
52
+ }
53
+
54
+ export interface CmsAiActionEvent {
55
+ type: 'action'
56
+ action: CmsAiAction
57
+ }
58
+
59
+ export type CmsAiAction =
60
+ | { name: 'refresh' }
61
+ | { name: 'preview'; url: string }
62
+ | { name: 'commit'; sha: string; message: string }
63
+ | { name: 'apply-edit'; elementId: string; content: string; htmlContent?: string }
64
+
65
+ export interface CmsAiErrorEvent {
66
+ type: 'error'
67
+ error: string
68
+ code?: string
69
+ }
70
+
71
+ export interface CmsAiDoneEvent {
72
+ type: 'done'
73
+ summary?: string
74
+ }
75
+
76
+ export interface CmsAiStreamCallbacks {
77
+ onToken?: (token: string, fullText: string) => void
78
+ onStatus?: (status: CmsAiStatusEvent['status'], message?: string) => void
79
+ onAction?: (action: CmsAiAction) => void
80
+ onError?: (error: string, code?: string) => void
81
+ onDone?: (summary?: string) => void
82
+ }
83
+
84
+ /**
85
+ * Create a fetch request with timeout
86
+ */
87
+ async function fetchWithTimeout(
88
+ url: string,
89
+ options: RequestInit = {},
90
+ timeoutMs: number = API.REQUEST_TIMEOUT_MS,
91
+ ): Promise<Response> {
92
+ const controller = new AbortController()
93
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
94
+
95
+ try {
96
+ const response = await fetch(url, {
97
+ ...options,
98
+ signal: controller.signal,
99
+ })
100
+ return response
101
+ } finally {
102
+ clearTimeout(timeoutId)
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get the manifest URL for the current page
108
+ * For example: /about -> /about.json
109
+ * / -> /index.json
110
+ * /blog/post -> /blog/post.json
111
+ */
112
+ export function getPageManifestUrl(pathname: string): string {
113
+ // Normalize path: remove trailing slash, default to 'index' for root
114
+ let path = pathname
115
+ if (path.length > 1 && path.endsWith('/')) {
116
+ path = path.slice(0, -1)
117
+ }
118
+ if (path === '/' || path === '') {
119
+ return '/index.json'
120
+ }
121
+ // Remove leading slash for the manifest path
122
+ const cleanPath = path.replace(/^\//, '')
123
+ return `/${cleanPath}.json`
124
+ }
125
+
126
+ /**
127
+ * Fetch manifest by combining page-specific and global manifests:
128
+ * 1. Per-page manifest (/{page}.json) - contains entries, components for this page
129
+ * 2. Global manifest (/cms-manifest.json) - contains componentDefinitions, availableColors
130
+ */
131
+ export async function fetchManifest(): Promise<CmsManifest> {
132
+ const pathname = window.location.pathname
133
+ const pageManifestUrl = getPageManifestUrl(pathname)
134
+ const globalManifestUrl = '/cms-manifest.json'
135
+
136
+ // Fetch both manifests in parallel with cache-busting to bypass CDN/edge caches
137
+ const cacheBuster = `_t=${Date.now()}`
138
+ const [pageResult, globalResult] = await Promise.allSettled([
139
+ fetchWithTimeout(`${pageManifestUrl}?${cacheBuster}`, { cache: 'no-store' }).then(res => res.ok ? res.json() : null),
140
+ fetchWithTimeout(`${globalManifestUrl}?${cacheBuster}`, { cache: 'no-store' }).then(res => res.ok ? res.json() : null),
141
+ ])
142
+
143
+ const pageManifest = pageResult.status === 'fulfilled' ? pageResult.value : null
144
+ const globalManifest = globalResult.status === 'fulfilled' ? globalResult.value : null
145
+
146
+ // Need at least one manifest to work with
147
+ if (!pageManifest && !globalManifest) {
148
+ throw new Error('Failed to load manifest from all sources')
149
+ }
150
+
151
+ // Get text styles from global manifest
152
+ const availableTextStyles = globalManifest?.availableTextStyles ?? pageManifest?.availableTextStyles
153
+
154
+ // Set text styles for inline style resolution
155
+ setAvailableTextStyles(availableTextStyles)
156
+
157
+ // Handle collection (singular) from page manifest - convert to collections format
158
+ let collections = pageManifest?.collections ?? {}
159
+ if (pageManifest?.collection) {
160
+ const entry = pageManifest.collection
161
+ const key = `${entry.collectionName}/${entry.collectionSlug}`
162
+ collections = { ...collections, [key]: entry }
163
+ }
164
+
165
+ // Merge manifests: page-specific data + global settings
166
+ return {
167
+ entries: pageManifest?.entries ?? {},
168
+ components: pageManifest?.components ?? {},
169
+ componentDefinitions: globalManifest?.componentDefinitions ?? pageManifest?.componentDefinitions ?? {},
170
+ collectionDefinitions: globalManifest?.collectionDefinitions ?? {},
171
+ collections,
172
+ availableColors: globalManifest?.availableColors ?? pageManifest?.availableColors,
173
+ availableTextStyles,
174
+ pages: globalManifest?.pages ?? pageManifest?.pages,
175
+ metadata: pageManifest?.metadata,
176
+ // SEO data from page-specific manifest
177
+ seo: pageManifest?.seo,
178
+ } as CmsManifest
179
+ }
180
+
181
+ export async function saveBatchChanges(
182
+ apiBase: string,
183
+ request: SaveBatchRequest,
184
+ ): Promise<SaveBatchResponse> {
185
+ const res = await fetchWithTimeout(`${apiBase}/update`, {
186
+ method: 'POST',
187
+ credentials: 'include',
188
+ headers: {
189
+ 'Content-Type': 'application/json',
190
+ },
191
+ body: JSON.stringify(request),
192
+ })
193
+
194
+ if (!res.ok) {
195
+ const text = await res.text().catch(() => '')
196
+ throw new Error(`Save failed (${res.status}): ${text || res.statusText}`)
197
+ }
198
+
199
+ return res.json().catch(() => ({ updated: 0 }))
200
+ }
201
+
202
+ export interface InsertComponentResponse {
203
+ success: boolean
204
+ message?: string
205
+ sourceFile?: string
206
+ commit?: string | null
207
+ commitMessage?: string
208
+ error?: string
209
+ }
210
+
211
+ export async function insertComponent(
212
+ apiBase: string,
213
+ operation: ComponentInsertOperation,
214
+ ): Promise<InsertComponentResponse> {
215
+ const res = await fetchWithTimeout(`${apiBase}/insert-component`, {
216
+ method: 'POST',
217
+ credentials: 'include',
218
+ headers: {
219
+ 'Content-Type': 'application/json',
220
+ },
221
+ body: JSON.stringify({
222
+ position: operation.position,
223
+ referenceComponentId: operation.referenceComponentId,
224
+ componentName: operation.componentName,
225
+ props: operation.props,
226
+ meta: {
227
+ source: 'inline-editor',
228
+ url: window.location.href,
229
+ },
230
+ }),
231
+ })
232
+
233
+ if (!res.ok) {
234
+ const text = await res.text().catch(() => '')
235
+ throw new Error(`Insert component failed (${res.status}): ${text || res.statusText}`)
236
+ }
237
+
238
+ return res.json()
239
+ }
240
+
241
+ export interface RemoveComponentResponse {
242
+ success: boolean
243
+ message?: string
244
+ sourceFile?: string
245
+ commit?: string | null
246
+ commitMessage?: string
247
+ error?: string
248
+ }
249
+
250
+ export async function removeComponent(
251
+ apiBase: string,
252
+ componentId: string,
253
+ ): Promise<RemoveComponentResponse> {
254
+ const res = await fetchWithTimeout(`${apiBase}/remove-component`, {
255
+ method: 'POST',
256
+ credentials: 'include',
257
+ headers: {
258
+ 'Content-Type': 'application/json',
259
+ },
260
+ body: JSON.stringify({
261
+ componentId,
262
+ meta: {
263
+ source: 'inline-editor',
264
+ url: window.location.href,
265
+ },
266
+ }),
267
+ })
268
+
269
+ if (!res.ok) {
270
+ const text = await res.text().catch(() => '')
271
+ throw new Error(`Remove component failed (${res.status}): ${text || res.statusText}`)
272
+ }
273
+
274
+ return res.json()
275
+ }
276
+
277
+ /**
278
+ * Parse SSE data line to event
279
+ */
280
+ export function parseSseEvent(data: string): CmsAiEvent | null {
281
+ if (data === '[DONE]') {
282
+ return { type: 'done' }
283
+ }
284
+ try {
285
+ return JSON.parse(data) as CmsAiEvent
286
+ } catch {
287
+ return null
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Stream AI chat response from the server using SSE
293
+ * Provides rich events including status updates, actions (refresh, preview), and streaming tokens
294
+ */
295
+ export async function streamAiChat(
296
+ apiBase: string,
297
+ request: CmsAiChatRequest,
298
+ callbacks: CmsAiStreamCallbacks,
299
+ abortSignal?: AbortSignal,
300
+ ): Promise<void> {
301
+ const controller = new AbortController()
302
+ const timeoutId = setTimeout(() => controller.abort(), API.AI_STREAM_TIMEOUT_MS)
303
+
304
+ // Allow external abort signal to also abort the request
305
+ if (abortSignal) {
306
+ abortSignal.addEventListener('abort', () => controller.abort())
307
+ }
308
+
309
+ let doneCalled = false
310
+
311
+ try {
312
+ const res = await fetch(`${apiBase}/ai/chat`, {
313
+ method: 'POST',
314
+ credentials: 'include',
315
+ headers: {
316
+ 'Content-Type': 'application/json',
317
+ },
318
+ body: JSON.stringify(request),
319
+ signal: controller.signal,
320
+ })
321
+
322
+ if (!res.ok) {
323
+ const text = await res.text().catch(() => '')
324
+ callbacks.onError?.(text || `Request failed (${res.status})`, String(res.status))
325
+ return
326
+ }
327
+
328
+ if (!res.body) {
329
+ callbacks.onError?.('No response body')
330
+ return
331
+ }
332
+
333
+ const reader = res.body.getReader()
334
+ const decoder = new TextDecoder()
335
+ let buffer = ''
336
+
337
+ while (true) {
338
+ const { done, value } = await reader.read()
339
+
340
+ if (done) {
341
+ break
342
+ }
343
+
344
+ buffer += decoder.decode(value, { stream: true })
345
+ const lines = buffer.split('\n')
346
+
347
+ // Keep the last incomplete line in the buffer
348
+ buffer = lines.pop() || ''
349
+
350
+ for (const line of lines) {
351
+ if (line.startsWith('data: ')) {
352
+ const data = line.slice(6).trim()
353
+ if (!data) continue
354
+
355
+ const event = parseSseEvent(data)
356
+ if (!event) continue
357
+
358
+ switch (event.type) {
359
+ case 'token':
360
+ callbacks.onToken?.(event.token, event.fullText)
361
+ break
362
+ case 'status':
363
+ callbacks.onStatus?.(event.status, event.message)
364
+ break
365
+ case 'action':
366
+ callbacks.onAction?.(event.action)
367
+ break
368
+ case 'error':
369
+ callbacks.onError?.(event.error, event.code)
370
+ break
371
+ case 'done':
372
+ if (!doneCalled) {
373
+ doneCalled = true
374
+ callbacks.onDone?.(event.summary)
375
+ }
376
+ return
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // Process any remaining buffer
383
+ if (buffer.startsWith('data: ')) {
384
+ const data = buffer.slice(6).trim()
385
+ if (data) {
386
+ const event = parseSseEvent(data)
387
+ if (event?.type === 'done' && !doneCalled) {
388
+ doneCalled = true
389
+ callbacks.onDone?.(event.summary)
390
+ }
391
+ }
392
+ }
393
+
394
+ if (!doneCalled) {
395
+ doneCalled = true
396
+ callbacks.onDone?.()
397
+ }
398
+ } catch (error) {
399
+ if (error instanceof Error && error.name === 'AbortError') {
400
+ callbacks.onError?.('Request timed out or was cancelled', 'TIMEOUT')
401
+ return
402
+ }
403
+ callbacks.onError?.(error instanceof Error ? error.message : 'Unknown error')
404
+ } finally {
405
+ clearTimeout(timeoutId)
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Chat message type from history endpoint
411
+ */
412
+ export interface ChatHistoryMessage {
413
+ id: string
414
+ role: 'user' | 'assistant' | 'tool'
415
+ content: string | null
416
+ created_at: string
417
+ channel?: string
418
+ identifier?: string
419
+ }
420
+
421
+ export interface ChatHistoryResponse {
422
+ messages: ChatHistoryMessage[]
423
+ hasMore: boolean
424
+ }
425
+
426
+ /**
427
+ * Fetch AI chat history for the current project
428
+ */
429
+ export async function getChatHistory(
430
+ apiBase: string,
431
+ limit = 50,
432
+ ): Promise<ChatHistoryResponse> {
433
+ const res = await fetchWithTimeout(`${apiBase}/ai/chat/history?limit=${limit}`, {
434
+ method: 'GET',
435
+ credentials: 'include',
436
+ })
437
+
438
+ if (!res.ok) {
439
+ const text = await res.text().catch(() => '')
440
+ throw new Error(`Failed to fetch chat history (${res.status}): ${text || res.statusText}`)
441
+ }
442
+
443
+ return res.json()
444
+ }
445
+
446
+ /**
447
+ * Fetch deployment status for the current project
448
+ */
449
+ export async function getDeploymentStatus(
450
+ apiBase: string,
451
+ ): Promise<DeploymentStatusResponse> {
452
+ const res = await fetchWithTimeout(`${apiBase}/deployment/status`, {
453
+ method: 'GET',
454
+ credentials: 'include',
455
+ })
456
+
457
+ if (!res.ok) {
458
+ const text = await res.text().catch(() => '')
459
+ throw new Error(`Failed to fetch deployment status (${res.status}): ${text || res.statusText}`)
460
+ }
461
+
462
+ return res.json()
463
+ }
464
+
465
+ /**
466
+ * Fetch markdown content from a file
467
+ */
468
+ export async function getMarkdownContent(
469
+ apiBase: string,
470
+ filePath: string,
471
+ ): Promise<GetMarkdownContentResponse | null> {
472
+ const res = await fetchWithTimeout(
473
+ `${apiBase}/markdown/content?filePath=${encodeURIComponent(filePath)}`,
474
+ {
475
+ method: 'GET',
476
+ credentials: 'include',
477
+ },
478
+ )
479
+
480
+ if (!res.ok) {
481
+ if (res.status === 404) {
482
+ return null
483
+ }
484
+ const text = await res.text().catch(() => '')
485
+ throw new Error(`Failed to fetch markdown content (${res.status}): ${text || res.statusText}`)
486
+ }
487
+
488
+ return res.json()
489
+ }
490
+
491
+ /**
492
+ * Update markdown page content
493
+ */
494
+ export async function updateMarkdownPage(
495
+ apiBase: string,
496
+ request: UpdateMarkdownPageRequest,
497
+ ): Promise<UpdateMarkdownPageResponse> {
498
+ const res = await fetchWithTimeout(`${apiBase}/markdown/update`, {
499
+ method: 'POST',
500
+ credentials: 'include',
501
+ headers: {
502
+ 'Content-Type': 'application/json',
503
+ },
504
+ body: JSON.stringify(request),
505
+ })
506
+
507
+ if (!res.ok) {
508
+ const text = await res.text().catch(() => '')
509
+ throw new Error(`Failed to update markdown page (${res.status}): ${text || res.statusText}`)
510
+ }
511
+
512
+ return res.json()
513
+ }