@templatical/core 0.0.6 → 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.
@@ -1,2774 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/cloud/index.ts
31
- var cloud_exports = {};
32
- __export(cloud_exports, {
33
- API_ROUTES: () => API_ROUTES,
34
- ApiClient: () => ApiClient,
35
- AuthManager: () => AuthManager,
36
- WebSocketClient: () => WebSocketClient,
37
- buildUrl: () => buildUrl,
38
- createSdkAuthManager: () => createSdkAuthManager,
39
- handleOperation: () => handleOperation,
40
- performHealthCheck: () => performHealthCheck,
41
- resolveWebSocketConfig: () => resolveWebSocketConfig,
42
- useAiChat: () => useAiChat,
43
- useAiConfig: () => useAiConfig,
44
- useAiRewrite: () => useAiRewrite,
45
- useCollaboration: () => useCollaboration,
46
- useCollaborationBroadcast: () => useCollaborationBroadcast,
47
- useCommentListener: () => useCommentListener,
48
- useComments: () => useComments,
49
- useDesignReference: () => useDesignReference,
50
- useEditor: () => useEditor,
51
- useExport: () => useExport,
52
- useMcpListener: () => useMcpListener,
53
- usePlanConfig: () => usePlanConfig,
54
- useSavedModules: () => useSavedModules,
55
- useSnapshotHistory: () => useSnapshotHistory,
56
- useTemplateScoring: () => useTemplateScoring,
57
- useTestEmail: () => useTestEmail,
58
- useWebSocket: () => useWebSocket
59
- });
60
- module.exports = __toCommonJS(cloud_exports);
61
-
62
- // src/cloud/auth.ts
63
- var import_types = require("@templatical/types");
64
- var AuthManager = class _AuthManager {
65
- static DEFAULT_BASE_URL = "https://templatical.com";
66
- accessToken = null;
67
- expiresAt = null;
68
- _projectId = null;
69
- _tenantId = null;
70
- _tenantSlug = null;
71
- _testEmailConfig = null;
72
- _userConfig = null;
73
- url;
74
- baseUrl;
75
- requestOptions;
76
- onError;
77
- refreshPromise = null;
78
- static REFRESH_THRESHOLD_MS = 60 * 1e3;
79
- constructor(config) {
80
- this.url = config.url;
81
- this.baseUrl = (config.baseUrl ?? _AuthManager.DEFAULT_BASE_URL).replace(
82
- /\/$/,
83
- ""
84
- );
85
- this.requestOptions = config.requestOptions ?? {};
86
- this.onError = config.onError;
87
- }
88
- resolveUrl(path) {
89
- if (path.startsWith("http://") || path.startsWith("https://")) {
90
- return path;
91
- }
92
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
93
- return `${this.baseUrl}${normalizedPath}`;
94
- }
95
- get projectId() {
96
- if (!this._projectId) {
97
- throw new Error("Project ID not available. Call initialize() first.");
98
- }
99
- return this._projectId;
100
- }
101
- get tenantId() {
102
- if (!this._tenantId) {
103
- throw new Error("Tenant ID not available. Call initialize() first.");
104
- }
105
- return this._tenantId;
106
- }
107
- get tenantSlug() {
108
- if (!this._tenantSlug) {
109
- throw new Error("Tenant slug not available. Call initialize() first.");
110
- }
111
- return this._tenantSlug;
112
- }
113
- get testEmailConfig() {
114
- return this._testEmailConfig;
115
- }
116
- get userConfig() {
117
- return this._userConfig;
118
- }
119
- get accessTokenValue() {
120
- return this.accessToken;
121
- }
122
- async initialize() {
123
- await this.ensureToken();
124
- }
125
- async ensureToken() {
126
- if (this.accessToken && !this.isTokenExpiringSoon()) {
127
- return this.accessToken;
128
- }
129
- return this.refreshToken();
130
- }
131
- isTokenExpiringSoon() {
132
- if (!this.expiresAt) {
133
- return true;
134
- }
135
- const timeUntilExpiry = this.expiresAt.getTime() - Date.now();
136
- return timeUntilExpiry < _AuthManager.REFRESH_THRESHOLD_MS;
137
- }
138
- async refreshToken() {
139
- if (this.refreshPromise) {
140
- return this.refreshPromise;
141
- }
142
- this.refreshPromise = this.performRefresh();
143
- try {
144
- const token = await this.refreshPromise;
145
- return token;
146
- } finally {
147
- this.refreshPromise = null;
148
- }
149
- }
150
- async performRefresh() {
151
- try {
152
- const method = this.requestOptions.method ?? "POST";
153
- const headers = {
154
- Accept: "application/json",
155
- ...this.requestOptions.headers
156
- };
157
- const fetchOptions = {
158
- method,
159
- headers,
160
- credentials: this.requestOptions.credentials ?? "include"
161
- };
162
- if (method === "POST" && this.requestOptions.body) {
163
- headers["Content-Type"] = "application/json";
164
- fetchOptions.body = JSON.stringify(this.requestOptions.body);
165
- }
166
- const response = await fetch(this.url, fetchOptions);
167
- if (!response.ok) {
168
- throw new import_types.SdkError(
169
- `Token refresh failed: ${response.status}`,
170
- response.status
171
- );
172
- }
173
- const data = await response.json();
174
- if (!data.token || !data.expires_at || !data.project_id || !data.tenant) {
175
- throw new Error(
176
- "Invalid token response: missing token, expires_at, project_id, or tenant"
177
- );
178
- }
179
- this.accessToken = data.token;
180
- this.expiresAt = new Date(data.expires_at * 1e3);
181
- this._projectId = data.project_id;
182
- this._tenantSlug = data.tenant;
183
- if (data.test_email?.allowed_emails && data.test_email?.signature) {
184
- this._testEmailConfig = {
185
- allowedEmails: data.test_email.allowed_emails,
186
- signature: data.test_email.signature
187
- };
188
- } else {
189
- this._testEmailConfig = null;
190
- }
191
- if (data.user?.id && data.user?.name && data.user?.signature) {
192
- this._userConfig = {
193
- id: data.user.id,
194
- name: data.user.name,
195
- signature: data.user.signature
196
- };
197
- } else {
198
- this._userConfig = null;
199
- }
200
- return this.accessToken;
201
- } catch (error) {
202
- const wrappedError = error instanceof Error ? error : new Error("Token refresh failed", { cause: error });
203
- this.onError?.(wrappedError);
204
- throw wrappedError;
205
- }
206
- }
207
- async authenticatedFetch(url, options = {}) {
208
- const token = await this.ensureToken();
209
- const resolvedUrl = this.resolveUrl(url);
210
- const makeRequest = async (authToken) => {
211
- return fetch(resolvedUrl, {
212
- ...options,
213
- headers: {
214
- ...options.headers,
215
- Authorization: `Bearer ${authToken}`
216
- }
217
- });
218
- };
219
- let response = await makeRequest(token);
220
- if (response.status === 401) {
221
- const newToken = await this.refreshToken();
222
- response = await makeRequest(newToken);
223
- }
224
- return response;
225
- }
226
- };
227
- function createSdkAuthManager(config, onError) {
228
- if (config.mode === "direct") {
229
- const baseUrl = (config.baseUrl ?? "https://templatical.com").replace(
230
- /\/$/,
231
- ""
232
- );
233
- return new AuthManager({
234
- url: `${baseUrl}/api/v1/auth/token`,
235
- baseUrl: config.baseUrl,
236
- requestOptions: {
237
- method: "POST",
238
- headers: {
239
- "Content-Type": "application/json"
240
- },
241
- body: {
242
- client_id: config.clientId,
243
- client_secret: config.clientSecret,
244
- tenant: config.tenant,
245
- client_type: "sdk"
246
- }
247
- },
248
- onError
249
- });
250
- }
251
- return new AuthManager({
252
- url: config.url,
253
- baseUrl: config.baseUrl,
254
- requestOptions: config.requestOptions,
255
- onError
256
- });
257
- }
258
-
259
- // src/cloud/api.ts
260
- var import_types2 = require("@templatical/types");
261
-
262
- // src/cloud/url-builder.ts
263
- function buildUrl(template, params) {
264
- return template.replace(
265
- /\{(\w+)\}/g,
266
- (_, key) => encodeURIComponent(params[key] ?? "")
267
- );
268
- }
269
- var BASE = "/api/v1/projects/{project}/tenants/{tenant}";
270
- var TEMPLATE = `${BASE}/templates/{template}`;
271
- var AI = `${TEMPLATE}/ai`;
272
- var MEDIA = `${BASE}/media`;
273
- var FOLDERS = `${MEDIA}/folders`;
274
- var MODULES = `${BASE}/saved-modules`;
275
- var API_ROUTES = {
276
- health: "/api/v1/health",
277
- "projects.config": `${BASE}/config`,
278
- "broadcasting.auth": `${BASE}/broadcasting/auth`,
279
- "templates.store": `${BASE}/templates`,
280
- "templates.show": `${TEMPLATE}`,
281
- "templates.update": `${TEMPLATE}`,
282
- "templates.destroy": `${TEMPLATE}`,
283
- "templates.export": `${TEMPLATE}/export`,
284
- "templates.importFromBeefree": `${BASE}/templates/import/from-beefree`,
285
- "templates.sendTestEmail": `${TEMPLATE}/send-test-email`,
286
- "snapshots.index": `${TEMPLATE}/snapshots`,
287
- "snapshots.store": `${TEMPLATE}/snapshots`,
288
- "snapshots.show": `${TEMPLATE}/snapshots/{snapshot}`,
289
- "snapshots.restore": `${TEMPLATE}/snapshots/{snapshot}/restore`,
290
- "comments.index": `${TEMPLATE}/comments`,
291
- "comments.store": `${TEMPLATE}/comments`,
292
- "comments.update": `${TEMPLATE}/comments/{comment}`,
293
- "comments.destroy": `${TEMPLATE}/comments/{comment}`,
294
- "comments.resolve": `${TEMPLATE}/comments/{comment}/resolve`,
295
- "ai.generate": `${AI}/generate`,
296
- "ai.conversationMessages": `${AI}/conversation-messages`,
297
- "ai.suggestions": `${AI}/suggestions`,
298
- "ai.rewriteText": `${AI}/rewrite-text`,
299
- "ai.score": `${AI}/score`,
300
- "ai.fixFinding": `${AI}/fix-finding`,
301
- "ai.generateFromDesign": `${AI}/generate-from-design`,
302
- "media.upload": `${MEDIA}/upload`,
303
- "media.browse": `${MEDIA}/browse`,
304
- "media.delete": `${MEDIA}/delete`,
305
- "media.move": `${MEDIA}/move`,
306
- "media.update": `${MEDIA}/{media}`,
307
- "media.replace": `${MEDIA}/{media}/replace`,
308
- "media.checkUsage": `${MEDIA}/check-usage`,
309
- "media.frequentlyUsed": `${MEDIA}/frequently-used`,
310
- "media.importFromUrl": `${MEDIA}/import-from-url`,
311
- "folders.index": `${FOLDERS}`,
312
- "folders.store": `${FOLDERS}`,
313
- "folders.update": `${FOLDERS}/{mediaFolder}`,
314
- "folders.destroy": `${FOLDERS}/{mediaFolder}`,
315
- "savedModules.index": `${MODULES}`,
316
- "savedModules.store": `${MODULES}`,
317
- "savedModules.update": `${MODULES}/{savedModule}`,
318
- "savedModules.destroy": `${MODULES}/{savedModule}`
319
- };
320
-
321
- // src/cloud/api.ts
322
- var ApiClient = class {
323
- constructor(authManager) {
324
- this.authManager = authManager;
325
- }
326
- get projectId() {
327
- return this.authManager.projectId;
328
- }
329
- get tenantSlug() {
330
- return this.authManager.tenantSlug;
331
- }
332
- get baseParams() {
333
- return { project: this.projectId, tenant: this.tenantSlug };
334
- }
335
- async request(path, options = {}) {
336
- const response = await this.authManager.authenticatedFetch(path, {
337
- ...options,
338
- headers: {
339
- "Content-Type": "application/json",
340
- Accept: "application/json",
341
- ...options.headers
342
- }
343
- });
344
- if (!response.ok) {
345
- const error = await response.json().catch(() => ({
346
- message: `HTTP error ${response.status}`
347
- }));
348
- const errorMessage = this.extractFirstValidationError(error);
349
- throw new import_types2.SdkError(errorMessage, response.status);
350
- }
351
- if (response.status === 204) {
352
- return void 0;
353
- }
354
- const json = await response.json();
355
- return json.data;
356
- }
357
- extractFirstValidationError(error) {
358
- if (error.errors) {
359
- const firstField = Object.keys(error.errors)[0];
360
- if (firstField && error.errors[firstField]?.length > 0) {
361
- return error.errors[firstField][0];
362
- }
363
- }
364
- return error.message;
365
- }
366
- async createTemplate(content) {
367
- return this.request(
368
- buildUrl(API_ROUTES["templates.store"], this.baseParams),
369
- {
370
- method: "POST",
371
- body: JSON.stringify({ content })
372
- }
373
- );
374
- }
375
- async getTemplate(id) {
376
- return this.request(
377
- buildUrl(API_ROUTES["templates.show"], {
378
- ...this.baseParams,
379
- template: id
380
- })
381
- );
382
- }
383
- async updateTemplate(id, content) {
384
- return this.request(
385
- buildUrl(API_ROUTES["templates.update"], {
386
- ...this.baseParams,
387
- template: id
388
- }),
389
- {
390
- method: "PUT",
391
- body: JSON.stringify({ content })
392
- }
393
- );
394
- }
395
- async createSnapshot(templateId, content) {
396
- return this.request(
397
- buildUrl(API_ROUTES["snapshots.store"], {
398
- ...this.baseParams,
399
- template: templateId
400
- }),
401
- {
402
- method: "POST",
403
- body: JSON.stringify({ content })
404
- }
405
- );
406
- }
407
- async deleteTemplate(id) {
408
- return this.request(
409
- buildUrl(API_ROUTES["templates.destroy"], {
410
- ...this.baseParams,
411
- template: id
412
- }),
413
- {
414
- method: "DELETE"
415
- }
416
- );
417
- }
418
- async getSnapshots(templateId) {
419
- return this.request(
420
- buildUrl(API_ROUTES["snapshots.index"], {
421
- ...this.baseParams,
422
- template: templateId
423
- })
424
- );
425
- }
426
- async restoreSnapshot(templateId, snapshotId) {
427
- return this.request(
428
- buildUrl(API_ROUTES["snapshots.restore"], {
429
- ...this.baseParams,
430
- template: templateId,
431
- snapshot: snapshotId
432
- }),
433
- {
434
- method: "POST"
435
- }
436
- );
437
- }
438
- async exportTemplate(templateId, fontsPayload) {
439
- const body = fontsPayload ? JSON.stringify({
440
- custom_fonts: fontsPayload.customFonts,
441
- default_fallback: fontsPayload.defaultFallback
442
- }) : void 0;
443
- return this.request(
444
- buildUrl(API_ROUTES["templates.export"], {
445
- ...this.baseParams,
446
- template: templateId
447
- }),
448
- {
449
- method: "POST",
450
- body
451
- }
452
- );
453
- }
454
- async sendTestEmail(templateId, payload) {
455
- await this.request(
456
- buildUrl(API_ROUTES["templates.sendTestEmail"], {
457
- ...this.baseParams,
458
- template: templateId
459
- }),
460
- {
461
- method: "POST",
462
- body: JSON.stringify(payload)
463
- }
464
- );
465
- }
466
- commentsUrl(templateId, commentId) {
467
- if (commentId) {
468
- return buildUrl(API_ROUTES["comments.update"], {
469
- ...this.baseParams,
470
- template: templateId,
471
- comment: commentId
472
- });
473
- }
474
- return buildUrl(API_ROUTES["comments.index"], {
475
- ...this.baseParams,
476
- template: templateId
477
- });
478
- }
479
- async getComments(templateId) {
480
- return this.request(this.commentsUrl(templateId));
481
- }
482
- async createComment(templateId, data, headers) {
483
- return this.request(this.commentsUrl(templateId), {
484
- method: "POST",
485
- body: JSON.stringify(data),
486
- headers
487
- });
488
- }
489
- async updateComment(templateId, commentId, data, headers) {
490
- return this.request(this.commentsUrl(templateId, commentId), {
491
- method: "PUT",
492
- body: JSON.stringify(data),
493
- headers
494
- });
495
- }
496
- async deleteComment(templateId, commentId, data, headers) {
497
- return this.request(this.commentsUrl(templateId, commentId), {
498
- method: "DELETE",
499
- body: JSON.stringify(data),
500
- headers
501
- });
502
- }
503
- async resolveComment(templateId, commentId, data, headers) {
504
- return this.request(
505
- buildUrl(API_ROUTES["comments.resolve"], {
506
- ...this.baseParams,
507
- template: templateId,
508
- comment: commentId
509
- }),
510
- {
511
- method: "POST",
512
- body: JSON.stringify(data),
513
- headers
514
- }
515
- );
516
- }
517
- async fetchConfig() {
518
- return this.request(
519
- buildUrl(API_ROUTES["projects.config"], this.baseParams)
520
- );
521
- }
522
- async listModules(search) {
523
- const url = buildUrl(API_ROUTES["savedModules.index"], this.baseParams);
524
- const query = search ? `?search=${encodeURIComponent(search)}` : "";
525
- return this.request(`${url}${query}`);
526
- }
527
- async createModule(data) {
528
- return this.request(
529
- buildUrl(API_ROUTES["savedModules.store"], this.baseParams),
530
- {
531
- method: "POST",
532
- body: JSON.stringify(data)
533
- }
534
- );
535
- }
536
- async updateModule(id, data) {
537
- return this.request(
538
- buildUrl(API_ROUTES["savedModules.update"], {
539
- ...this.baseParams,
540
- savedModule: id
541
- }),
542
- {
543
- method: "PUT",
544
- body: JSON.stringify(data)
545
- }
546
- );
547
- }
548
- async deleteModule(id) {
549
- return this.request(
550
- buildUrl(API_ROUTES["savedModules.destroy"], {
551
- ...this.baseParams,
552
- savedModule: id
553
- }),
554
- {
555
- method: "DELETE"
556
- }
557
- );
558
- }
559
- };
560
-
561
- // src/cloud/websocket-client.ts
562
- function resolveWebSocketConfig(serverConfig) {
563
- return {
564
- host: serverConfig.host,
565
- port: serverConfig.port,
566
- appKey: serverConfig.app_key
567
- };
568
- }
569
- var WebSocketClient = class {
570
- pusher = null;
571
- authManager;
572
- config;
573
- onError;
574
- constructor(options) {
575
- this.authManager = options.authManager;
576
- this.config = options.config;
577
- this.onError = options.onError;
578
- }
579
- async connect() {
580
- if (this.pusher) {
581
- return;
582
- }
583
- const { default: Pusher } = await import("pusher-js");
584
- const { host, port, appKey } = this.config;
585
- const authEndpoint = this.authManager.resolveUrl(
586
- buildUrl(API_ROUTES["broadcasting.auth"], {
587
- project: this.authManager.projectId,
588
- tenant: this.authManager.tenantSlug
589
- })
590
- );
591
- this.pusher = new Pusher(appKey, {
592
- wsHost: host,
593
- wsPort: port,
594
- wssPort: port,
595
- forceTLS: true,
596
- disableStats: true,
597
- enabledTransports: ["ws", "wss"],
598
- cluster: "",
599
- channelAuthorization: {
600
- transport: "ajax",
601
- endpoint: authEndpoint,
602
- headers: {
603
- Authorization: `Bearer ${this.authManager.accessTokenValue}`,
604
- Accept: "application/json"
605
- },
606
- params: {
607
- user_id: this.authManager.userConfig?.id ?? "",
608
- user_name: this.authManager.userConfig?.name ?? "",
609
- user_signature: this.authManager.userConfig?.signature ?? ""
610
- }
611
- }
612
- });
613
- this.pusher.connection.bind("error", (error) => {
614
- this.onError?.(
615
- error instanceof Error ? error : new Error("WebSocket connection error")
616
- );
617
- });
618
- }
619
- subscribePresence(channelName) {
620
- if (!this.pusher) {
621
- throw new Error("WebSocketClient not connected. Call connect() first.");
622
- }
623
- return this.pusher.subscribe(channelName);
624
- }
625
- unsubscribe(channelName) {
626
- this.pusher?.unsubscribe(channelName);
627
- }
628
- getChannel(channelName) {
629
- return this.pusher?.channel(channelName);
630
- }
631
- disconnect() {
632
- if (this.pusher) {
633
- this.pusher.disconnect();
634
- this.pusher = null;
635
- }
636
- }
637
- getSocketId() {
638
- return this.pusher?.connection.socket_id ?? null;
639
- }
640
- get isConnected() {
641
- return this.pusher?.connection.state === "connected";
642
- }
643
- };
644
-
645
- // src/cloud/mcp-operation-handler.ts
646
- function handleOperation(editor, payload) {
647
- const { operation, data } = payload;
648
- switch (operation) {
649
- case "add_block":
650
- editor.addBlock(
651
- data.block,
652
- data.section_id,
653
- data.column_index,
654
- data.index
655
- );
656
- break;
657
- case "update_block":
658
- editor.updateBlock(
659
- data.block_id,
660
- data.updates
661
- );
662
- break;
663
- case "delete_block":
664
- editor.removeBlock(data.block_id);
665
- break;
666
- case "move_block":
667
- editor.moveBlock(
668
- data.block_id,
669
- data.index,
670
- data.section_id,
671
- data.column_index
672
- );
673
- break;
674
- case "update_settings":
675
- editor.updateSettings(data.updates);
676
- break;
677
- case "set_content":
678
- editor.setContent(data.content);
679
- break;
680
- case "update_block_style":
681
- editor.updateBlock(
682
- data.block_id,
683
- {
684
- styles: data.styles
685
- }
686
- );
687
- break;
688
- }
689
- }
690
-
691
- // src/cloud/editor.ts
692
- var import_types3 = require("@templatical/types");
693
- var import_types4 = require("@templatical/types");
694
- var import_vue = require("vue");
695
- function useEditor(options) {
696
- const api = new ApiClient(options.authManager);
697
- const state = (0, import_vue.reactive)({
698
- template: null,
699
- content: (0, import_types4.createDefaultTemplateContent)(
700
- options.defaultFontFamily,
701
- options.templateDefaults
702
- ),
703
- selectedBlockId: null,
704
- viewport: "desktop",
705
- darkMode: false,
706
- previewMode: false,
707
- isDirty: false,
708
- isSaving: false,
709
- isLoading: false,
710
- uiTheme: "auto"
711
- });
712
- const content = (0, import_vue.computed)({
713
- get: () => state.content,
714
- set: (value) => {
715
- state.content = value;
716
- state.isDirty = true;
717
- }
718
- });
719
- const selectedBlock = (0, import_vue.computed)(() => {
720
- if (!state.selectedBlockId) return null;
721
- return findBlockById(state.content.blocks, state.selectedBlockId);
722
- });
723
- const savedBlockIds = (0, import_vue.computed)(() => {
724
- const ids = /* @__PURE__ */ new Set();
725
- const blocks = state.template?.content?.blocks;
726
- if (!blocks) {
727
- return ids;
728
- }
729
- for (const block of blocks) {
730
- ids.add(block.id);
731
- if (block.type === "section") {
732
- for (const column of block.children) {
733
- for (const child of column) {
734
- ids.add(child.id);
735
- }
736
- }
737
- }
738
- }
739
- return ids;
740
- });
741
- function findBlockById(blocks, id) {
742
- for (const block of blocks) {
743
- if (block.id === id) return block;
744
- if (block.type === "section") {
745
- for (const column of block.children) {
746
- const found = findBlockById(column, id);
747
- if (found) return found;
748
- }
749
- }
750
- }
751
- return null;
752
- }
753
- function findBlockParent(blocks, id, parent = { blocks }) {
754
- for (let i = 0; i < blocks.length; i++) {
755
- const block = blocks[i];
756
- if (block.id === id) return parent;
757
- if (block.type === "section") {
758
- for (let colIdx = 0; colIdx < block.children.length; colIdx++) {
759
- const result = findBlockParent(block.children[colIdx], id, {
760
- blocks: block.children[colIdx],
761
- sectionId: block.id,
762
- columnIndex: colIdx
763
- });
764
- if (result) return result;
765
- }
766
- }
767
- }
768
- return null;
769
- }
770
- function isBlockLocked(blockId) {
771
- return options.lockedBlocks?.value.has(blockId) ?? false;
772
- }
773
- function setContent(newContent, markDirty2 = true) {
774
- state.content = newContent;
775
- if (markDirty2) {
776
- state.isDirty = true;
777
- }
778
- }
779
- function selectBlock(blockId) {
780
- if (blockId && isBlockLocked(blockId)) {
781
- return;
782
- }
783
- state.selectedBlockId = blockId;
784
- }
785
- function setViewport(viewport) {
786
- state.viewport = viewport;
787
- }
788
- function setDarkMode(darkMode) {
789
- state.darkMode = darkMode;
790
- }
791
- function setUiTheme(theme) {
792
- state.uiTheme = theme;
793
- }
794
- function setPreviewMode(previewMode) {
795
- state.previewMode = previewMode;
796
- if (previewMode) {
797
- state.selectedBlockId = null;
798
- }
799
- }
800
- function updateBlock(blockId, updates) {
801
- if (isBlockLocked(blockId)) {
802
- return;
803
- }
804
- const block = findBlockById(state.content.blocks, blockId);
805
- if (block) {
806
- Object.assign(block, updates);
807
- state.isDirty = true;
808
- }
809
- }
810
- function updateSettings(updates) {
811
- state.content.settings = { ...state.content.settings, ...updates };
812
- state.isDirty = true;
813
- }
814
- function addBlock(block, targetSectionId, columnIndex = 0, index) {
815
- if (targetSectionId) {
816
- const section = findBlockById(state.content.blocks, targetSectionId);
817
- if (section && section.type === "section") {
818
- section.children[columnIndex] = section.children[columnIndex] || [];
819
- const targetArray = section.children[columnIndex];
820
- if (index !== void 0 && index < targetArray.length) {
821
- targetArray.splice(index, 0, block);
822
- } else {
823
- targetArray.push(block);
824
- }
825
- }
826
- } else {
827
- if (index !== void 0 && index < state.content.blocks.length) {
828
- state.content.blocks.splice(index, 0, block);
829
- } else {
830
- state.content.blocks.push(block);
831
- }
832
- }
833
- state.isDirty = true;
834
- }
835
- function removeBlock(blockId) {
836
- if (isBlockLocked(blockId)) {
837
- return;
838
- }
839
- const parent = findBlockParent(state.content.blocks, blockId);
840
- if (parent) {
841
- const index = parent.blocks.findIndex((b) => b.id === blockId);
842
- if (index !== -1) {
843
- parent.blocks.splice(index, 1);
844
- if (state.selectedBlockId === blockId) {
845
- state.selectedBlockId = null;
846
- }
847
- state.isDirty = true;
848
- }
849
- }
850
- }
851
- function moveBlock(blockId, newIndex, targetSectionId, columnIndex = 0) {
852
- const parent = findBlockParent(state.content.blocks, blockId);
853
- if (!parent) return;
854
- const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
855
- if (oldIndex === -1) return;
856
- const [block] = parent.blocks.splice(oldIndex, 1);
857
- if (targetSectionId) {
858
- const section = findBlockById(state.content.blocks, targetSectionId);
859
- if (section && section.type === "section") {
860
- section.children[columnIndex] = section.children[columnIndex] || [];
861
- section.children[columnIndex].splice(newIndex, 0, block);
862
- }
863
- } else {
864
- state.content.blocks.splice(newIndex, 0, block);
865
- }
866
- state.isDirty = true;
867
- }
868
- async function create(content2) {
869
- state.isLoading = true;
870
- try {
871
- if (content2) {
872
- state.content = content2;
873
- }
874
- const template = await api.createTemplate(state.content);
875
- state.template = template;
876
- state.isDirty = false;
877
- return template;
878
- } catch (error) {
879
- options.onError?.(error);
880
- throw error;
881
- } finally {
882
- state.isLoading = false;
883
- }
884
- }
885
- async function load(templateId) {
886
- state.isLoading = true;
887
- try {
888
- const template = await api.getTemplate(templateId);
889
- state.template = template;
890
- state.content = template.content;
891
- state.isDirty = false;
892
- return template;
893
- } catch (error) {
894
- options.onError?.(error);
895
- throw error;
896
- } finally {
897
- state.isLoading = false;
898
- }
899
- }
900
- async function save() {
901
- if (!state.template?.id) {
902
- throw new import_types3.SdkError(
903
- "No template loaded. Call create() or load() before saving."
904
- );
905
- }
906
- state.isSaving = true;
907
- try {
908
- const template = await api.updateTemplate(
909
- state.template.id,
910
- state.content
911
- );
912
- state.template = template;
913
- state.isDirty = false;
914
- return template;
915
- } catch (error) {
916
- options.onError?.(error);
917
- throw error;
918
- } finally {
919
- state.isSaving = false;
920
- }
921
- }
922
- async function createSnapshot() {
923
- if (!state.template?.id) {
924
- return;
925
- }
926
- try {
927
- await api.createSnapshot(state.template.id, state.content);
928
- } catch (error) {
929
- options.onError?.(error);
930
- throw error;
931
- }
932
- }
933
- function hasTemplate() {
934
- return state.template?.id !== void 0;
935
- }
936
- function markDirty() {
937
- state.isDirty = true;
938
- }
939
- return {
940
- state: (0, import_vue.readonly)(state),
941
- content,
942
- selectedBlock,
943
- savedBlockIds,
944
- isBlockLocked,
945
- setContent,
946
- selectBlock,
947
- setViewport,
948
- setDarkMode,
949
- setUiTheme,
950
- setPreviewMode,
951
- updateBlock,
952
- updateSettings,
953
- addBlock,
954
- removeBlock,
955
- moveBlock,
956
- create,
957
- load,
958
- save,
959
- createSnapshot,
960
- hasTemplate,
961
- markDirty
962
- };
963
- }
964
-
965
- // src/cloud/ai-chat.ts
966
- var import_vue2 = require("vue");
967
- var messageIdCounter = 0;
968
- function generateMessageId() {
969
- return `msg_${Date.now()}_${++messageIdCounter}`;
970
- }
971
- function useAiChat(options) {
972
- const { authManager, getTemplateId, onApply, onError } = options;
973
- const messages = (0, import_vue2.ref)([]);
974
- const isGenerating = (0, import_vue2.ref)(false);
975
- const isLoadingHistory = (0, import_vue2.ref)(false);
976
- const error = (0, import_vue2.ref)(null);
977
- const failedPrompt = (0, import_vue2.ref)(null);
978
- const conversationId = (0, import_vue2.ref)(null);
979
- const lastApplyMessageId = (0, import_vue2.ref)(null);
980
- const lastPreviousContent = (0, import_vue2.ref)(null);
981
- const lastAppliedContent = (0, import_vue2.ref)(null);
982
- const isLastChangeReverted = (0, import_vue2.ref)(false);
983
- const suggestions = (0, import_vue2.ref)([]);
984
- const isLoadingSuggestions = (0, import_vue2.ref)(false);
985
- function updateMessage(msgId, updates) {
986
- const idx = messages.value.findIndex((m) => m.id === msgId);
987
- if (idx === -1) {
988
- return;
989
- }
990
- const updated = { ...messages.value[idx], ...updates };
991
- messages.value = [
992
- ...messages.value.slice(0, idx),
993
- updated,
994
- ...messages.value.slice(idx + 1)
995
- ];
996
- }
997
- async function loadConversation() {
998
- const templateId = getTemplateId();
999
- if (!templateId) {
1000
- return;
1001
- }
1002
- isLoadingHistory.value = true;
1003
- try {
1004
- const url = buildUrl(API_ROUTES["ai.conversationMessages"], {
1005
- project: authManager.projectId,
1006
- tenant: authManager.tenantSlug,
1007
- template: templateId
1008
- });
1009
- const response = await authManager.authenticatedFetch(url, {
1010
- method: "GET",
1011
- headers: {
1012
- Accept: "application/json"
1013
- }
1014
- });
1015
- if (!response.ok) {
1016
- return;
1017
- }
1018
- const data = await response.json();
1019
- if (data.conversation_id) {
1020
- conversationId.value = data.conversation_id;
1021
- }
1022
- if (Array.isArray(data.data) && data.data.length > 0) {
1023
- messages.value = data.data.map(
1024
- (msg) => ({
1025
- id: msg.id,
1026
- role: msg.role,
1027
- content: msg.content,
1028
- timestamp: new Date(msg.created_at).getTime()
1029
- })
1030
- );
1031
- }
1032
- } catch {
1033
- } finally {
1034
- isLoadingHistory.value = false;
1035
- }
1036
- }
1037
- async function loadSuggestions(currentContent, mergeTags) {
1038
- const templateId = getTemplateId();
1039
- if (!templateId) {
1040
- return;
1041
- }
1042
- isLoadingSuggestions.value = true;
1043
- try {
1044
- const url = buildUrl(API_ROUTES["ai.suggestions"], {
1045
- project: authManager.projectId,
1046
- tenant: authManager.tenantSlug,
1047
- template: templateId
1048
- });
1049
- const response = await authManager.authenticatedFetch(url, {
1050
- method: "POST",
1051
- headers: {
1052
- "Content-Type": "application/json",
1053
- Accept: "text/event-stream"
1054
- },
1055
- body: JSON.stringify({
1056
- current_content: currentContent,
1057
- merge_tags: mergeTags.map((p) => ({
1058
- label: p.label,
1059
- value: p.value
1060
- }))
1061
- })
1062
- });
1063
- if (!response.ok) {
1064
- return;
1065
- }
1066
- const reader = response.body?.getReader();
1067
- if (!reader) {
1068
- return;
1069
- }
1070
- const decoder = new TextDecoder();
1071
- let buffer = "";
1072
- while (true) {
1073
- const { done, value } = await reader.read();
1074
- if (done) {
1075
- break;
1076
- }
1077
- buffer += decoder.decode(value, { stream: true });
1078
- const lines = buffer.split("\n");
1079
- buffer = lines.pop() ?? "";
1080
- for (const line of lines) {
1081
- if (!line.startsWith("data: ")) {
1082
- continue;
1083
- }
1084
- let event;
1085
- try {
1086
- event = JSON.parse(line.slice(6));
1087
- } catch {
1088
- continue;
1089
- }
1090
- if (event.type === "done" && Array.isArray(event.suggestions)) {
1091
- suggestions.value = event.suggestions.slice(0, 3);
1092
- }
1093
- }
1094
- }
1095
- } catch {
1096
- } finally {
1097
- isLoadingSuggestions.value = false;
1098
- }
1099
- }
1100
- async function sendPrompt(prompt, currentContent, mergeTags) {
1101
- const templateId = getTemplateId();
1102
- if (!templateId) {
1103
- throw new Error("Template must be saved before using AI generation");
1104
- }
1105
- isGenerating.value = true;
1106
- error.value = null;
1107
- failedPrompt.value = null;
1108
- suggestions.value = [];
1109
- const userMsgId = generateMessageId();
1110
- messages.value = [
1111
- ...messages.value,
1112
- {
1113
- id: userMsgId,
1114
- role: "user",
1115
- content: prompt,
1116
- timestamp: Date.now()
1117
- }
1118
- ];
1119
- const assistantMsgId = generateMessageId();
1120
- messages.value = [
1121
- ...messages.value,
1122
- {
1123
- id: assistantMsgId,
1124
- role: "assistant",
1125
- content: "",
1126
- timestamp: Date.now()
1127
- }
1128
- ];
1129
- try {
1130
- const url = buildUrl(API_ROUTES["ai.generate"], {
1131
- project: authManager.projectId,
1132
- tenant: authManager.tenantSlug,
1133
- template: templateId
1134
- });
1135
- const response = await authManager.authenticatedFetch(url, {
1136
- method: "POST",
1137
- headers: {
1138
- "Content-Type": "application/json",
1139
- Accept: "text/event-stream"
1140
- },
1141
- body: JSON.stringify({
1142
- prompt,
1143
- current_content: currentContent,
1144
- merge_tags: mergeTags.map((p) => ({
1145
- label: p.label,
1146
- value: p.value
1147
- })),
1148
- conversation_id: conversationId.value
1149
- })
1150
- });
1151
- if (!response.ok) {
1152
- const errorData = await response.json().catch(() => null);
1153
- if (response.status === 403) {
1154
- throw new Error("ai_generation_not_available");
1155
- }
1156
- throw new Error(errorData?.message || "Failed to generate template");
1157
- }
1158
- const reader = response.body?.getReader();
1159
- if (!reader) {
1160
- throw new Error("Failed to read stream");
1161
- }
1162
- const decoder = new TextDecoder();
1163
- let buffer = "";
1164
- let result = null;
1165
- while (true) {
1166
- const { done, value } = await reader.read();
1167
- if (done) {
1168
- break;
1169
- }
1170
- buffer += decoder.decode(value, { stream: true });
1171
- const lines = buffer.split("\n");
1172
- buffer = lines.pop() ?? "";
1173
- for (const line of lines) {
1174
- if (!line.startsWith("data: ")) {
1175
- continue;
1176
- }
1177
- const jsonStr = line.slice(6);
1178
- let event;
1179
- try {
1180
- event = JSON.parse(jsonStr);
1181
- } catch {
1182
- continue;
1183
- }
1184
- if (event.type === "text") {
1185
- updateMessage(assistantMsgId, {
1186
- content: (messages.value.find((m) => m.id === assistantMsgId)?.content ?? "") + event.text
1187
- });
1188
- } else if (event.type === "error") {
1189
- throw new Error(event.message || "Failed to generate template");
1190
- } else if (event.type === "done") {
1191
- if (event.conversation_id) {
1192
- conversationId.value = event.conversation_id;
1193
- }
1194
- updateMessage(assistantMsgId, {
1195
- content: event.text
1196
- });
1197
- result = event.content ?? null;
1198
- if (result) {
1199
- lastPreviousContent.value = currentContent;
1200
- lastAppliedContent.value = result;
1201
- lastApplyMessageId.value = assistantMsgId;
1202
- isLastChangeReverted.value = false;
1203
- onApply?.(result);
1204
- } else {
1205
- error.value = "ai_apply_failed";
1206
- }
1207
- }
1208
- }
1209
- }
1210
- return result;
1211
- } catch (err) {
1212
- const wrappedError = err instanceof Error ? err : new Error("Failed to generate template", { cause: err });
1213
- error.value = wrappedError.message;
1214
- failedPrompt.value = prompt;
1215
- onError?.(wrappedError);
1216
- messages.value = messages.value.filter(
1217
- (m) => m.id !== userMsgId && m.id !== assistantMsgId
1218
- );
1219
- return null;
1220
- } finally {
1221
- isGenerating.value = false;
1222
- }
1223
- }
1224
- function toggleLastRevert() {
1225
- if (isLastChangeReverted.value) {
1226
- if (lastAppliedContent.value) {
1227
- onApply?.(lastAppliedContent.value);
1228
- }
1229
- isLastChangeReverted.value = false;
1230
- } else {
1231
- if (lastPreviousContent.value) {
1232
- onApply?.(lastPreviousContent.value);
1233
- }
1234
- isLastChangeReverted.value = true;
1235
- }
1236
- }
1237
- function clearChat() {
1238
- messages.value = [];
1239
- conversationId.value = null;
1240
- error.value = null;
1241
- lastApplyMessageId.value = null;
1242
- lastPreviousContent.value = null;
1243
- lastAppliedContent.value = null;
1244
- isLastChangeReverted.value = false;
1245
- }
1246
- return {
1247
- messages,
1248
- isGenerating,
1249
- isLoadingHistory,
1250
- isLastChangeReverted,
1251
- lastApplyMessageId,
1252
- error,
1253
- failedPrompt,
1254
- suggestions,
1255
- isLoadingSuggestions,
1256
- sendPrompt,
1257
- toggleLastRevert,
1258
- loadConversation,
1259
- loadSuggestions,
1260
- clearChat
1261
- };
1262
- }
1263
-
1264
- // src/cloud/ai-rewrite.ts
1265
- var import_vue3 = require("vue");
1266
- function useAiRewrite(options) {
1267
- const { authManager, getTemplateId } = options;
1268
- const isRewriting = (0, import_vue3.ref)(false);
1269
- const streamingText = (0, import_vue3.ref)("");
1270
- const previousContent = (0, import_vue3.ref)(null);
1271
- const rewrittenContent = (0, import_vue3.ref)(null);
1272
- const isReverted = (0, import_vue3.ref)(false);
1273
- const error = (0, import_vue3.ref)(null);
1274
- async function rewrite(content, instruction, mergeTags) {
1275
- const templateId = getTemplateId();
1276
- if (!templateId) {
1277
- return null;
1278
- }
1279
- isRewriting.value = true;
1280
- streamingText.value = "";
1281
- error.value = null;
1282
- try {
1283
- const url = buildUrl(API_ROUTES["ai.rewriteText"], {
1284
- project: authManager.projectId,
1285
- tenant: authManager.tenantSlug,
1286
- template: templateId
1287
- });
1288
- const response = await authManager.authenticatedFetch(url, {
1289
- method: "POST",
1290
- headers: {
1291
- "Content-Type": "application/json",
1292
- Accept: "text/event-stream"
1293
- },
1294
- body: JSON.stringify({
1295
- content,
1296
- instruction,
1297
- merge_tags: mergeTags.map((p) => ({
1298
- label: p.label,
1299
- value: p.value
1300
- }))
1301
- })
1302
- });
1303
- if (!response.ok) {
1304
- if (response.status === 403) {
1305
- throw new Error("ai_generation_not_available");
1306
- }
1307
- const errorData = await response.json().catch(() => null);
1308
- throw new Error(errorData?.message || "Failed to rewrite text");
1309
- }
1310
- const reader = response.body?.getReader();
1311
- if (!reader) {
1312
- throw new Error("Failed to read stream");
1313
- }
1314
- const decoder = new TextDecoder();
1315
- let buffer = "";
1316
- let result = null;
1317
- while (true) {
1318
- const { done, value } = await reader.read();
1319
- if (done) {
1320
- break;
1321
- }
1322
- buffer += decoder.decode(value, { stream: true });
1323
- const lines = buffer.split("\n");
1324
- buffer = lines.pop() ?? "";
1325
- for (const line of lines) {
1326
- if (!line.startsWith("data: ")) {
1327
- continue;
1328
- }
1329
- let event;
1330
- try {
1331
- event = JSON.parse(line.slice(6));
1332
- } catch {
1333
- continue;
1334
- }
1335
- if (event.type === "text") {
1336
- streamingText.value += event.text;
1337
- } else if (event.type === "error") {
1338
- throw new Error(event.message || "Failed to rewrite text");
1339
- } else if (event.type === "done") {
1340
- result = event.content ?? null;
1341
- if (result) {
1342
- previousContent.value = content;
1343
- rewrittenContent.value = result;
1344
- isReverted.value = false;
1345
- }
1346
- }
1347
- }
1348
- }
1349
- return result;
1350
- } catch (err) {
1351
- error.value = err instanceof Error ? err.message : "Failed to rewrite text";
1352
- return null;
1353
- } finally {
1354
- isRewriting.value = false;
1355
- }
1356
- }
1357
- function undo() {
1358
- if (!previousContent.value) {
1359
- return null;
1360
- }
1361
- isReverted.value = true;
1362
- return previousContent.value;
1363
- }
1364
- function redo() {
1365
- if (!rewrittenContent.value) {
1366
- return null;
1367
- }
1368
- isReverted.value = false;
1369
- return rewrittenContent.value;
1370
- }
1371
- function reset() {
1372
- isRewriting.value = false;
1373
- streamingText.value = "";
1374
- previousContent.value = null;
1375
- rewrittenContent.value = null;
1376
- isReverted.value = false;
1377
- error.value = null;
1378
- }
1379
- return {
1380
- isRewriting,
1381
- streamingText,
1382
- previousContent,
1383
- rewrittenContent,
1384
- isReverted,
1385
- error,
1386
- rewrite,
1387
- undo,
1388
- redo,
1389
- reset
1390
- };
1391
- }
1392
-
1393
- // src/cloud/ai-config.ts
1394
- var import_vue4 = require("vue");
1395
- function useAiConfig(config) {
1396
- function isFeatureEnabled(feature) {
1397
- if (config === false) {
1398
- return false;
1399
- }
1400
- return config?.[feature] !== false;
1401
- }
1402
- const hasAnyMenuFeature = (0, import_vue4.computed)(
1403
- () => isFeatureEnabled("chat") || isFeatureEnabled("scoring") || isFeatureEnabled("designToTemplate")
1404
- );
1405
- return {
1406
- isFeatureEnabled,
1407
- hasAnyMenuFeature
1408
- };
1409
- }
1410
-
1411
- // src/cloud/template-scoring.ts
1412
- var import_vue5 = require("vue");
1413
- function useTemplateScoring(options) {
1414
- const { authManager, getTemplateId } = options;
1415
- const isScoring = (0, import_vue5.ref)(false);
1416
- const scoringResult = (0, import_vue5.ref)(null);
1417
- const error = (0, import_vue5.ref)(null);
1418
- const fixingFindingId = (0, import_vue5.ref)(null);
1419
- const fixStreamingText = (0, import_vue5.ref)("");
1420
- const fixError = (0, import_vue5.ref)(null);
1421
- async function score(content, mergeTags) {
1422
- const templateId = getTemplateId();
1423
- if (!templateId) {
1424
- return null;
1425
- }
1426
- isScoring.value = true;
1427
- error.value = null;
1428
- scoringResult.value = null;
1429
- try {
1430
- const url = buildUrl(API_ROUTES["ai.score"], {
1431
- project: authManager.projectId,
1432
- tenant: authManager.tenantSlug,
1433
- template: templateId
1434
- });
1435
- const response = await authManager.authenticatedFetch(url, {
1436
- method: "POST",
1437
- headers: {
1438
- "Content-Type": "application/json",
1439
- Accept: "text/event-stream"
1440
- },
1441
- body: JSON.stringify({
1442
- current_content: content,
1443
- merge_tags: mergeTags.map((p) => ({
1444
- label: p.label,
1445
- value: p.value
1446
- }))
1447
- })
1448
- });
1449
- if (!response.ok) {
1450
- if (response.status === 403) {
1451
- throw new Error("ai_generation_not_available");
1452
- }
1453
- const errorData = await response.json().catch(() => null);
1454
- throw new Error(errorData?.message || "Failed to score template");
1455
- }
1456
- const reader = response.body?.getReader();
1457
- if (!reader) {
1458
- throw new Error("Failed to read stream");
1459
- }
1460
- const decoder = new TextDecoder();
1461
- let buffer = "";
1462
- let result = null;
1463
- while (true) {
1464
- const { done, value } = await reader.read();
1465
- if (done) {
1466
- break;
1467
- }
1468
- buffer += decoder.decode(value, { stream: true });
1469
- const lines = buffer.split("\n");
1470
- buffer = lines.pop() ?? "";
1471
- for (const line of lines) {
1472
- if (!line.startsWith("data: ")) {
1473
- continue;
1474
- }
1475
- let event;
1476
- try {
1477
- event = JSON.parse(line.slice(6));
1478
- } catch {
1479
- continue;
1480
- }
1481
- if (event.type === "error") {
1482
- throw new Error(event.message || "Failed to score template");
1483
- }
1484
- if (event.type === "done") {
1485
- result = event.result ?? null;
1486
- }
1487
- }
1488
- }
1489
- if (result) {
1490
- for (const [category, categoryData] of Object.entries(
1491
- result.categories
1492
- )) {
1493
- for (const finding of categoryData.findings) {
1494
- finding.category = category;
1495
- }
1496
- }
1497
- }
1498
- scoringResult.value = result;
1499
- return result;
1500
- } catch (err) {
1501
- error.value = err instanceof Error ? err.message : "Failed to score template";
1502
- return null;
1503
- } finally {
1504
- isScoring.value = false;
1505
- }
1506
- }
1507
- async function fixFinding(blockContent, finding, mergeTags) {
1508
- const templateId = getTemplateId();
1509
- if (!templateId) {
1510
- return null;
1511
- }
1512
- fixingFindingId.value = finding.id;
1513
- fixStreamingText.value = "";
1514
- fixError.value = null;
1515
- try {
1516
- const url = buildUrl(API_ROUTES["ai.fixFinding"], {
1517
- project: authManager.projectId,
1518
- tenant: authManager.tenantSlug,
1519
- template: templateId
1520
- });
1521
- const response = await authManager.authenticatedFetch(url, {
1522
- method: "POST",
1523
- headers: {
1524
- "Content-Type": "application/json",
1525
- Accept: "text/event-stream"
1526
- },
1527
- body: JSON.stringify({
1528
- content: blockContent,
1529
- finding: {
1530
- id: finding.id,
1531
- message: finding.message,
1532
- suggestion: finding.suggestion,
1533
- category: finding.category
1534
- },
1535
- merge_tags: mergeTags.map((p) => ({
1536
- label: p.label,
1537
- value: p.value
1538
- }))
1539
- })
1540
- });
1541
- if (!response.ok) {
1542
- if (response.status === 403) {
1543
- throw new Error("ai_generation_not_available");
1544
- }
1545
- const errorData = await response.json().catch(() => null);
1546
- throw new Error(errorData?.message || "Failed to fix finding");
1547
- }
1548
- const reader = response.body?.getReader();
1549
- if (!reader) {
1550
- throw new Error("Failed to read stream");
1551
- }
1552
- const decoder = new TextDecoder();
1553
- let buffer = "";
1554
- let result = null;
1555
- while (true) {
1556
- const { done, value } = await reader.read();
1557
- if (done) {
1558
- break;
1559
- }
1560
- buffer += decoder.decode(value, { stream: true });
1561
- const lines = buffer.split("\n");
1562
- buffer = lines.pop() ?? "";
1563
- for (const line of lines) {
1564
- if (!line.startsWith("data: ")) {
1565
- continue;
1566
- }
1567
- let event;
1568
- try {
1569
- event = JSON.parse(line.slice(6));
1570
- } catch {
1571
- continue;
1572
- }
1573
- if (event.type === "text") {
1574
- fixStreamingText.value += event.text;
1575
- } else if (event.type === "error") {
1576
- throw new Error(event.message || "Failed to fix finding");
1577
- } else if (event.type === "done") {
1578
- result = event.content ?? null;
1579
- }
1580
- }
1581
- }
1582
- return result;
1583
- } catch (err) {
1584
- fixError.value = err instanceof Error ? err.message : "Failed to fix finding";
1585
- return null;
1586
- } finally {
1587
- fixingFindingId.value = null;
1588
- }
1589
- }
1590
- function removeFinding(category, findingId) {
1591
- if (!scoringResult.value) {
1592
- return;
1593
- }
1594
- const cat = scoringResult.value.categories[category];
1595
- if (!cat) {
1596
- return;
1597
- }
1598
- cat.findings = cat.findings.filter((f) => f.id !== findingId);
1599
- }
1600
- function reset() {
1601
- isScoring.value = false;
1602
- scoringResult.value = null;
1603
- error.value = null;
1604
- fixingFindingId.value = null;
1605
- fixStreamingText.value = "";
1606
- fixError.value = null;
1607
- }
1608
- return {
1609
- isScoring,
1610
- scoringResult,
1611
- error,
1612
- fixingFindingId,
1613
- fixStreamingText,
1614
- fixError,
1615
- score,
1616
- fixFinding,
1617
- removeFinding,
1618
- reset
1619
- };
1620
- }
1621
-
1622
- // src/cloud/design-reference.ts
1623
- var import_vue6 = require("vue");
1624
- function useDesignReference(options) {
1625
- const { authManager, getTemplateId, onApply, onError } = options;
1626
- const isGenerating = (0, import_vue6.ref)(false);
1627
- const error = (0, import_vue6.ref)(null);
1628
- async function generate(input) {
1629
- const templateId = getTemplateId();
1630
- if (!templateId) {
1631
- throw new Error("Template must be saved before using design reference");
1632
- }
1633
- isGenerating.value = true;
1634
- error.value = null;
1635
- try {
1636
- const formData = new FormData();
1637
- if (input.prompt) {
1638
- formData.append("prompt", input.prompt);
1639
- }
1640
- if (input.imageUpload) {
1641
- formData.append("image_upload", input.imageUpload);
1642
- }
1643
- if (input.pdfUpload) {
1644
- formData.append("pdf_upload", input.pdfUpload);
1645
- }
1646
- const url = buildUrl(API_ROUTES["ai.generateFromDesign"], {
1647
- project: authManager.projectId,
1648
- tenant: authManager.tenantSlug,
1649
- template: templateId
1650
- });
1651
- const response = await authManager.authenticatedFetch(url, {
1652
- method: "POST",
1653
- headers: {
1654
- Accept: "text/event-stream"
1655
- },
1656
- body: formData
1657
- });
1658
- if (!response.ok) {
1659
- const errorData = await response.json().catch(() => null);
1660
- if (response.status === 403) {
1661
- throw new Error("ai_generation_not_available");
1662
- }
1663
- throw new Error(
1664
- errorData?.message || "Failed to generate template from design"
1665
- );
1666
- }
1667
- const reader = response.body?.getReader();
1668
- if (!reader) {
1669
- throw new Error("Failed to read stream");
1670
- }
1671
- const decoder = new TextDecoder();
1672
- let buffer = "";
1673
- let result = null;
1674
- while (true) {
1675
- const { done, value } = await reader.read();
1676
- if (done) {
1677
- break;
1678
- }
1679
- buffer += decoder.decode(value, { stream: true });
1680
- const lines = buffer.split("\n");
1681
- buffer = lines.pop() ?? "";
1682
- for (const line of lines) {
1683
- if (!line.startsWith("data: ")) {
1684
- continue;
1685
- }
1686
- const jsonStr = line.slice(6);
1687
- let event;
1688
- try {
1689
- event = JSON.parse(jsonStr);
1690
- } catch {
1691
- continue;
1692
- }
1693
- if (event.type === "error") {
1694
- throw new Error(
1695
- event.message || "Failed to generate template from design"
1696
- );
1697
- }
1698
- if (event.type === "done") {
1699
- result = event.content ?? null;
1700
- if (result) {
1701
- onApply?.(result);
1702
- }
1703
- }
1704
- }
1705
- }
1706
- return result;
1707
- } catch (err) {
1708
- const wrappedError = err instanceof Error ? err : new Error("Failed to generate template from design", {
1709
- cause: err
1710
- });
1711
- error.value = wrappedError.message;
1712
- onError?.(wrappedError);
1713
- return null;
1714
- } finally {
1715
- isGenerating.value = false;
1716
- }
1717
- }
1718
- function reset() {
1719
- isGenerating.value = false;
1720
- error.value = null;
1721
- }
1722
- return {
1723
- isGenerating,
1724
- error,
1725
- generate,
1726
- reset
1727
- };
1728
- }
1729
-
1730
- // src/cloud/comments.ts
1731
- var import_vue7 = require("vue");
1732
- function useComments(options) {
1733
- const {
1734
- authManager,
1735
- getTemplateId,
1736
- getSocketId,
1737
- onComment,
1738
- onError,
1739
- hasCommentingFeature
1740
- } = options;
1741
- const api = new ApiClient(authManager);
1742
- const comments = (0, import_vue7.ref)([]);
1743
- const isLoading = (0, import_vue7.ref)(false);
1744
- const isSubmitting = (0, import_vue7.ref)(false);
1745
- const isEnabled = (0, import_vue7.computed)(() => {
1746
- const featureAvailable = hasCommentingFeature?.() ?? false;
1747
- return featureAvailable && authManager.userConfig !== null;
1748
- });
1749
- const totalCount = (0, import_vue7.computed)(() => {
1750
- let count = 0;
1751
- for (const thread of comments.value) {
1752
- count += 1 + (thread.replies?.length ?? 0);
1753
- }
1754
- return count;
1755
- });
1756
- const unresolvedCount = (0, import_vue7.computed)(() => {
1757
- return comments.value.filter((c) => !c.resolved_at).length;
1758
- });
1759
- const commentCountByBlock = (0, import_vue7.computed)(() => {
1760
- const map = /* @__PURE__ */ new Map();
1761
- for (const thread of comments.value) {
1762
- if (thread.block_id) {
1763
- map.set(
1764
- thread.block_id,
1765
- (map.get(thread.block_id) ?? 0) + 1 + (thread.replies?.length ?? 0)
1766
- );
1767
- }
1768
- }
1769
- return map;
1770
- });
1771
- function getUserPayload() {
1772
- const user = authManager.userConfig;
1773
- if (!user) {
1774
- throw new Error("User config not available");
1775
- }
1776
- return {
1777
- user_id: user.id,
1778
- user_name: user.name,
1779
- user_signature: user.signature
1780
- };
1781
- }
1782
- function socketHeaders() {
1783
- const socketId = getSocketId?.();
1784
- if (!socketId) {
1785
- return void 0;
1786
- }
1787
- return { "X-Socket-ID": socketId };
1788
- }
1789
- function emitEvent(type, comment) {
1790
- onComment?.({ type, comment });
1791
- }
1792
- function findComment(commentId) {
1793
- for (const thread of comments.value) {
1794
- if (thread.id === commentId) {
1795
- return thread;
1796
- }
1797
- for (const reply of thread.replies ?? []) {
1798
- if (reply.id === commentId) {
1799
- return reply;
1800
- }
1801
- }
1802
- }
1803
- return null;
1804
- }
1805
- async function loadComments() {
1806
- const templateId = getTemplateId();
1807
- if (!templateId) {
1808
- return;
1809
- }
1810
- isLoading.value = true;
1811
- try {
1812
- comments.value = await api.getComments(templateId);
1813
- } catch (err) {
1814
- const wrappedError = err instanceof Error ? err : new Error("Failed to load comments", { cause: err });
1815
- onError?.(wrappedError);
1816
- } finally {
1817
- isLoading.value = false;
1818
- }
1819
- }
1820
- async function addComment(body, blockId, parentId) {
1821
- const templateId = getTemplateId();
1822
- if (!templateId) {
1823
- return null;
1824
- }
1825
- isSubmitting.value = true;
1826
- try {
1827
- const comment = await api.createComment(
1828
- templateId,
1829
- {
1830
- body,
1831
- block_id: blockId,
1832
- parent_id: parentId,
1833
- ...getUserPayload()
1834
- },
1835
- socketHeaders()
1836
- );
1837
- if (parentId) {
1838
- const parent = findComment(parentId);
1839
- if (parent) {
1840
- parent.replies = [...parent.replies ?? [], comment];
1841
- }
1842
- } else {
1843
- comments.value = [...comments.value, comment];
1844
- }
1845
- emitEvent("created", comment);
1846
- return comment;
1847
- } catch (err) {
1848
- const wrappedError = err instanceof Error ? err : new Error("Failed to create comment", { cause: err });
1849
- onError?.(wrappedError);
1850
- return null;
1851
- } finally {
1852
- isSubmitting.value = false;
1853
- }
1854
- }
1855
- async function editComment(commentId, body) {
1856
- const templateId = getTemplateId();
1857
- if (!templateId) {
1858
- return null;
1859
- }
1860
- isSubmitting.value = true;
1861
- try {
1862
- const updated = await api.updateComment(
1863
- templateId,
1864
- commentId,
1865
- {
1866
- body,
1867
- ...getUserPayload()
1868
- },
1869
- socketHeaders()
1870
- );
1871
- updateCommentInState(commentId, updated);
1872
- emitEvent("updated", updated);
1873
- return updated;
1874
- } catch (err) {
1875
- const wrappedError = err instanceof Error ? err : new Error("Failed to update comment", { cause: err });
1876
- onError?.(wrappedError);
1877
- return null;
1878
- } finally {
1879
- isSubmitting.value = false;
1880
- }
1881
- }
1882
- async function removeComment(commentId) {
1883
- const templateId = getTemplateId();
1884
- if (!templateId) {
1885
- return false;
1886
- }
1887
- const comment = findComment(commentId);
1888
- if (!comment) {
1889
- return false;
1890
- }
1891
- const commentSnapshot = {
1892
- ...comment,
1893
- replies: [...comment.replies ?? []]
1894
- };
1895
- isSubmitting.value = true;
1896
- try {
1897
- await api.deleteComment(
1898
- templateId,
1899
- commentId,
1900
- getUserPayload(),
1901
- socketHeaders()
1902
- );
1903
- if (comment.parent_id) {
1904
- const parent = findComment(comment.parent_id);
1905
- if (parent) {
1906
- parent.replies = (parent.replies ?? []).filter(
1907
- (r) => r.id !== commentId
1908
- );
1909
- }
1910
- } else {
1911
- comments.value = comments.value.filter((c) => c.id !== commentId);
1912
- }
1913
- emitEvent("deleted", commentSnapshot);
1914
- return true;
1915
- } catch (err) {
1916
- const wrappedError = err instanceof Error ? err : new Error("Failed to delete comment", { cause: err });
1917
- onError?.(wrappedError);
1918
- return false;
1919
- } finally {
1920
- isSubmitting.value = false;
1921
- }
1922
- }
1923
- async function toggleResolve(commentId) {
1924
- const templateId = getTemplateId();
1925
- if (!templateId) {
1926
- return null;
1927
- }
1928
- isSubmitting.value = true;
1929
- try {
1930
- const updated = await api.resolveComment(
1931
- templateId,
1932
- commentId,
1933
- getUserPayload(),
1934
- socketHeaders()
1935
- );
1936
- updateCommentInState(commentId, updated);
1937
- const eventType = updated.resolved_at ? "resolved" : "unresolved";
1938
- emitEvent(eventType, updated);
1939
- return updated;
1940
- } catch (err) {
1941
- const wrappedError = err instanceof Error ? err : new Error("Failed to toggle comment resolution", { cause: err });
1942
- onError?.(wrappedError);
1943
- return null;
1944
- } finally {
1945
- isSubmitting.value = false;
1946
- }
1947
- }
1948
- function applyRemoteCreate(comment) {
1949
- if (comment.parent_id) {
1950
- const parent = findComment(comment.parent_id);
1951
- if (parent) {
1952
- parent.replies = [...parent.replies ?? [], comment];
1953
- }
1954
- } else {
1955
- comments.value = [...comments.value, comment];
1956
- }
1957
- emitEvent("created", comment);
1958
- }
1959
- function applyRemoteUpdate(comment) {
1960
- updateCommentInState(comment.id, comment);
1961
- emitEvent("updated", comment);
1962
- }
1963
- function applyRemoteDelete(commentId, parentId) {
1964
- const comment = findComment(commentId);
1965
- const snapshot = comment ? { ...comment, replies: [...comment.replies ?? []] } : null;
1966
- if (parentId) {
1967
- const parent = findComment(parentId);
1968
- if (parent) {
1969
- parent.replies = (parent.replies ?? []).filter(
1970
- (r) => r.id !== commentId
1971
- );
1972
- }
1973
- } else {
1974
- comments.value = comments.value.filter((c) => c.id !== commentId);
1975
- }
1976
- if (snapshot) {
1977
- emitEvent("deleted", snapshot);
1978
- }
1979
- }
1980
- function updateCommentInState(commentId, updated) {
1981
- for (let i = 0; i < comments.value.length; i++) {
1982
- if (comments.value[i].id === commentId) {
1983
- comments.value = [
1984
- ...comments.value.slice(0, i),
1985
- { ...updated, replies: comments.value[i].replies },
1986
- ...comments.value.slice(i + 1)
1987
- ];
1988
- return;
1989
- }
1990
- const replies = comments.value[i].replies ?? [];
1991
- for (let j = 0; j < replies.length; j++) {
1992
- if (replies[j].id === commentId) {
1993
- const newReplies = [
1994
- ...replies.slice(0, j),
1995
- updated,
1996
- ...replies.slice(j + 1)
1997
- ];
1998
- comments.value = [
1999
- ...comments.value.slice(0, i),
2000
- { ...comments.value[i], replies: newReplies },
2001
- ...comments.value.slice(i + 1)
2002
- ];
2003
- return;
2004
- }
2005
- }
2006
- }
2007
- }
2008
- return {
2009
- comments,
2010
- isLoading,
2011
- isSubmitting,
2012
- isEnabled,
2013
- commentCountByBlock,
2014
- totalCount,
2015
- unresolvedCount,
2016
- loadComments,
2017
- addComment,
2018
- editComment,
2019
- removeComment,
2020
- toggleResolve,
2021
- applyRemoteCreate,
2022
- applyRemoteUpdate,
2023
- applyRemoteDelete
2024
- };
2025
- }
2026
-
2027
- // src/cloud/comment-listener.ts
2028
- var import_vue8 = require("vue");
2029
- function useCommentListener(options) {
2030
- const { comments, channel } = options;
2031
- (0, import_vue8.watch)(channel, (newChannel, oldChannel) => {
2032
- if (oldChannel) {
2033
- oldChannel.unbind("comment-broadcast");
2034
- }
2035
- if (newChannel) {
2036
- newChannel.bind(
2037
- "comment-broadcast",
2038
- (payload) => {
2039
- handleCommentBroadcast(comments, payload);
2040
- }
2041
- );
2042
- }
2043
- });
2044
- }
2045
- function handleCommentBroadcast(comments, payload) {
2046
- switch (payload.action) {
2047
- case "comment_created":
2048
- comments.applyRemoteCreate(payload.comment);
2049
- break;
2050
- case "comment_updated":
2051
- comments.applyRemoteUpdate(payload.comment);
2052
- break;
2053
- case "comment_deleted":
2054
- comments.applyRemoteDelete(payload.comment.id, payload.comment.parent_id);
2055
- break;
2056
- case "comment_resolved":
2057
- case "comment_unresolved":
2058
- comments.applyRemoteUpdate(payload.comment);
2059
- break;
2060
- }
2061
- }
2062
-
2063
- // src/cloud/collaboration.ts
2064
- var import_vue9 = require("vue");
2065
- var COLLABORATOR_COLORS = [
2066
- "#3b82f6",
2067
- "#ef4444",
2068
- "#10b981",
2069
- "#f59e0b",
2070
- "#8b5cf6",
2071
- "#ec4899",
2072
- "#06b6d4",
2073
- "#f97316",
2074
- "#6366f1",
2075
- "#14b8a6"
2076
- ];
2077
- function useCollaboration(options) {
2078
- const { authManager, editor, channel } = options;
2079
- const collaborators = (0, import_vue9.ref)([]);
2080
- const lockedBlocks = (0, import_vue9.ref)(/* @__PURE__ */ new Map());
2081
- let colorIndex = 0;
2082
- let isProcessingRemoteOperation = false;
2083
- const myUserId = (0, import_vue9.computed)(() => authManager.userConfig?.id ?? "");
2084
- function assignColor() {
2085
- const color = COLLABORATOR_COLORS[colorIndex % COLLABORATOR_COLORS.length];
2086
- colorIndex++;
2087
- return color;
2088
- }
2089
- function addCollaborator(member) {
2090
- if (member.id === myUserId.value) {
2091
- return void 0;
2092
- }
2093
- if (collaborators.value.some((c) => c.id === member.id)) {
2094
- return void 0;
2095
- }
2096
- const collaborator = {
2097
- id: member.id,
2098
- name: member.name,
2099
- color: assignColor(),
2100
- selectedBlockId: null
2101
- };
2102
- collaborators.value = [...collaborators.value, collaborator];
2103
- return collaborator;
2104
- }
2105
- function removeCollaborator(memberId) {
2106
- const newLockedBlocks = new Map(lockedBlocks.value);
2107
- for (const [blockId, collaborator] of newLockedBlocks) {
2108
- if (collaborator.id === memberId) {
2109
- newLockedBlocks.delete(blockId);
2110
- }
2111
- }
2112
- lockedBlocks.value = newLockedBlocks;
2113
- collaborators.value = collaborators.value.filter((c) => c.id !== memberId);
2114
- }
2115
- function handleBlockLocked(data) {
2116
- const collaborator = collaborators.value.find((c) => c.id === data.userId);
2117
- if (!collaborator) {
2118
- return;
2119
- }
2120
- collaborators.value = collaborators.value.map(
2121
- (c) => c.id === data.userId ? { ...c, selectedBlockId: data.blockId } : c
2122
- );
2123
- const newLockedBlocks = new Map(lockedBlocks.value);
2124
- for (const [blockId, holder] of newLockedBlocks) {
2125
- if (holder.id === data.userId) {
2126
- newLockedBlocks.delete(blockId);
2127
- }
2128
- }
2129
- newLockedBlocks.set(data.blockId, {
2130
- ...collaborator,
2131
- selectedBlockId: data.blockId
2132
- });
2133
- lockedBlocks.value = newLockedBlocks;
2134
- if (editor.state.selectedBlockId === data.blockId) {
2135
- editor.selectBlock(null);
2136
- }
2137
- }
2138
- function handleBlockUnlocked(data) {
2139
- const newLockedBlocks = new Map(lockedBlocks.value);
2140
- const holder = newLockedBlocks.get(data.blockId);
2141
- newLockedBlocks.delete(data.blockId);
2142
- lockedBlocks.value = newLockedBlocks;
2143
- if (holder) {
2144
- collaborators.value = collaborators.value.map(
2145
- (c) => c.id === holder.id ? { ...c, selectedBlockId: null } : c
2146
- );
2147
- }
2148
- }
2149
- function handleRemoteOperation(payload) {
2150
- isProcessingRemoteOperation = true;
2151
- try {
2152
- handleOperation(editor, payload);
2153
- } finally {
2154
- isProcessingRemoteOperation = false;
2155
- }
2156
- }
2157
- function broadcastOperation(payload) {
2158
- if (!channel.value || isProcessingRemoteOperation) {
2159
- return;
2160
- }
2161
- channel.value.trigger("client-operation", payload);
2162
- }
2163
- function broadcastBlockLocked(blockId) {
2164
- if (!channel.value) {
2165
- return;
2166
- }
2167
- channel.value.trigger("client-block_locked", {
2168
- blockId,
2169
- userId: myUserId.value
2170
- });
2171
- }
2172
- function broadcastBlockUnlocked(blockId) {
2173
- if (!channel.value) {
2174
- return;
2175
- }
2176
- channel.value.trigger("client-block_unlocked", { blockId });
2177
- }
2178
- (0, import_vue9.watch)(
2179
- () => editor.state.selectedBlockId,
2180
- (newBlockId, oldBlockId) => {
2181
- if (isProcessingRemoteOperation) {
2182
- return;
2183
- }
2184
- if (oldBlockId) {
2185
- broadcastBlockUnlocked(oldBlockId);
2186
- }
2187
- if (newBlockId) {
2188
- broadcastBlockLocked(newBlockId);
2189
- }
2190
- }
2191
- );
2192
- (0, import_vue9.watch)(channel, (newChannel, oldChannel) => {
2193
- if (oldChannel) {
2194
- oldChannel.unbind("pusher:member_added");
2195
- oldChannel.unbind("pusher:member_removed");
2196
- oldChannel.unbind("client-block_locked");
2197
- oldChannel.unbind("client-block_unlocked");
2198
- oldChannel.unbind("client-operation");
2199
- oldChannel.unbind("mcp-operation");
2200
- }
2201
- if (!newChannel) {
2202
- collaborators.value = [];
2203
- lockedBlocks.value = /* @__PURE__ */ new Map();
2204
- colorIndex = 0;
2205
- return;
2206
- }
2207
- const members = newChannel.members;
2208
- if (members) {
2209
- members.each((member) => {
2210
- addCollaborator(member.info);
2211
- });
2212
- }
2213
- newChannel.bind(
2214
- "pusher:member_added",
2215
- (member) => {
2216
- const collaborator = addCollaborator(member.info);
2217
- if (collaborator) {
2218
- options.onCollaboratorJoined?.(collaborator);
2219
- }
2220
- }
2221
- );
2222
- newChannel.bind(
2223
- "pusher:member_removed",
2224
- (member) => {
2225
- const collaborator = collaborators.value.find(
2226
- (c) => c.id === member.id
2227
- );
2228
- removeCollaborator(member.id);
2229
- if (collaborator) {
2230
- options.onCollaboratorLeft?.(collaborator);
2231
- }
2232
- }
2233
- );
2234
- newChannel.bind(
2235
- "client-block_locked",
2236
- (data) => {
2237
- handleBlockLocked(data);
2238
- const collaborator = collaborators.value.find(
2239
- (c) => c.id === data.userId
2240
- );
2241
- if (collaborator) {
2242
- options.onBlockLocked?.({
2243
- blockId: data.blockId,
2244
- collaborator
2245
- });
2246
- }
2247
- }
2248
- );
2249
- newChannel.bind("client-block_unlocked", (data) => {
2250
- const holder = lockedBlocks.value.get(data.blockId);
2251
- handleBlockUnlocked(data);
2252
- if (holder) {
2253
- options.onBlockUnlocked?.({
2254
- blockId: data.blockId,
2255
- collaborator: holder
2256
- });
2257
- }
2258
- });
2259
- newChannel.bind("client-operation", (payload) => {
2260
- handleRemoteOperation(payload);
2261
- });
2262
- newChannel.bind("mcp-operation", (payload) => {
2263
- handleRemoteOperation(payload);
2264
- });
2265
- });
2266
- return {
2267
- collaborators,
2268
- lockedBlocks,
2269
- _broadcastOperation: broadcastOperation,
2270
- _isProcessingRemoteOperation: () => isProcessingRemoteOperation
2271
- };
2272
- }
2273
-
2274
- // src/cloud/collaboration-broadcast.ts
2275
- function useCollaborationBroadcast(editor, collaboration) {
2276
- const originalAddBlock = editor.addBlock;
2277
- const originalUpdateBlock = editor.updateBlock;
2278
- const originalRemoveBlock = editor.removeBlock;
2279
- const originalMoveBlock = editor.moveBlock;
2280
- const originalUpdateSettings = editor.updateSettings;
2281
- const originalSetContent = editor.setContent;
2282
- editor.addBlock = (block, targetSectionId, columnIndex) => {
2283
- originalAddBlock(block, targetSectionId, columnIndex);
2284
- collaboration._broadcastOperation({
2285
- operation: "add_block",
2286
- data: {
2287
- block,
2288
- section_id: targetSectionId,
2289
- column_index: columnIndex
2290
- },
2291
- timestamp: Date.now()
2292
- });
2293
- };
2294
- editor.updateBlock = (blockId, updates) => {
2295
- originalUpdateBlock(blockId, updates);
2296
- collaboration._broadcastOperation({
2297
- operation: "update_block",
2298
- data: { block_id: blockId, updates },
2299
- timestamp: Date.now()
2300
- });
2301
- };
2302
- editor.removeBlock = (blockId) => {
2303
- originalRemoveBlock(blockId);
2304
- collaboration._broadcastOperation({
2305
- operation: "delete_block",
2306
- data: { block_id: blockId },
2307
- timestamp: Date.now()
2308
- });
2309
- };
2310
- editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
2311
- originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
2312
- collaboration._broadcastOperation({
2313
- operation: "move_block",
2314
- data: {
2315
- block_id: blockId,
2316
- index: newIndex,
2317
- section_id: targetSectionId,
2318
- column_index: columnIndex
2319
- },
2320
- timestamp: Date.now()
2321
- });
2322
- };
2323
- editor.updateSettings = (updates) => {
2324
- originalUpdateSettings(updates);
2325
- collaboration._broadcastOperation({
2326
- operation: "update_settings",
2327
- data: { updates },
2328
- timestamp: Date.now()
2329
- });
2330
- };
2331
- editor.setContent = (content, markDirty) => {
2332
- originalSetContent(content, markDirty);
2333
- collaboration._broadcastOperation({
2334
- operation: "set_content",
2335
- data: { content },
2336
- timestamp: Date.now()
2337
- });
2338
- };
2339
- }
2340
-
2341
- // src/cloud/web-socket.ts
2342
- var import_vue10 = require("vue");
2343
- function useWebSocket(options) {
2344
- const { authManager, onError } = options;
2345
- const channel = (0, import_vue10.ref)(
2346
- null
2347
- );
2348
- const isConnected = (0, import_vue10.ref)(false);
2349
- let wsClient = null;
2350
- let channelName = null;
2351
- async function connect(templateId, config) {
2352
- if (wsClient) {
2353
- return;
2354
- }
2355
- wsClient = new WebSocketClient({
2356
- authManager,
2357
- config,
2358
- onError
2359
- });
2360
- await wsClient.connect();
2361
- channelName = `presence-template.${templateId}`;
2362
- const presenceChannel = wsClient.subscribePresence(channelName);
2363
- presenceChannel.bind("pusher:subscription_succeeded", () => {
2364
- isConnected.value = true;
2365
- channel.value = presenceChannel;
2366
- });
2367
- presenceChannel.bind("pusher:subscription_error", (error) => {
2368
- isConnected.value = false;
2369
- channel.value = null;
2370
- onError?.(
2371
- error instanceof Error ? error : new Error("Failed to subscribe to template channel")
2372
- );
2373
- });
2374
- }
2375
- function disconnect() {
2376
- if (channelName && wsClient) {
2377
- wsClient.unsubscribe(channelName);
2378
- }
2379
- wsClient?.disconnect();
2380
- wsClient = null;
2381
- channelName = null;
2382
- channel.value = null;
2383
- isConnected.value = false;
2384
- }
2385
- function getSocketId() {
2386
- return wsClient?.getSocketId() ?? null;
2387
- }
2388
- return {
2389
- channel,
2390
- isConnected,
2391
- connect,
2392
- disconnect,
2393
- getSocketId
2394
- };
2395
- }
2396
-
2397
- // src/cloud/saved-modules.ts
2398
- var import_vue11 = require("vue");
2399
- function useSavedModules(options) {
2400
- const api = new ApiClient(options.authManager);
2401
- const modules = (0, import_vue11.ref)([]);
2402
- const isLoading = (0, import_vue11.ref)(false);
2403
- async function loadModules(search) {
2404
- isLoading.value = true;
2405
- try {
2406
- modules.value = await api.listModules(search);
2407
- } catch (error) {
2408
- options.onError?.(error);
2409
- throw error;
2410
- } finally {
2411
- isLoading.value = false;
2412
- }
2413
- }
2414
- async function createModule(name, content) {
2415
- try {
2416
- const module2 = await api.createModule({ name, content });
2417
- modules.value = [module2, ...modules.value];
2418
- return module2;
2419
- } catch (error) {
2420
- options.onError?.(error);
2421
- throw error;
2422
- }
2423
- }
2424
- async function updateModule(id, data) {
2425
- try {
2426
- const updated = await api.updateModule(id, data);
2427
- modules.value = modules.value.map((m) => m.id === id ? updated : m);
2428
- return updated;
2429
- } catch (error) {
2430
- options.onError?.(error);
2431
- throw error;
2432
- }
2433
- }
2434
- async function deleteModule(id) {
2435
- try {
2436
- await api.deleteModule(id);
2437
- modules.value = modules.value.filter((m) => m.id !== id);
2438
- } catch (error) {
2439
- options.onError?.(error);
2440
- throw error;
2441
- }
2442
- }
2443
- return {
2444
- modules,
2445
- isLoading,
2446
- loadModules,
2447
- createModule,
2448
- updateModule,
2449
- deleteModule
2450
- };
2451
- }
2452
-
2453
- // src/cloud/snapshots.ts
2454
- var import_vue12 = require("vue");
2455
- function useSnapshotHistory(options) {
2456
- const api = new ApiClient(options.authManager);
2457
- const snapshots = (0, import_vue12.ref)([]);
2458
- const isLoading = (0, import_vue12.ref)(false);
2459
- const isRestoring = (0, import_vue12.ref)(false);
2460
- async function loadSnapshots() {
2461
- isLoading.value = true;
2462
- try {
2463
- snapshots.value = await api.getSnapshots(options.templateId);
2464
- } catch (error) {
2465
- options.onError?.(error);
2466
- throw error;
2467
- } finally {
2468
- isLoading.value = false;
2469
- }
2470
- }
2471
- async function restoreSnapshot(snapshotId) {
2472
- isRestoring.value = true;
2473
- try {
2474
- const template = await api.restoreSnapshot(
2475
- options.templateId,
2476
- snapshotId
2477
- );
2478
- options.onRestore?.(template);
2479
- return template;
2480
- } catch (error) {
2481
- options.onError?.(error);
2482
- throw error;
2483
- } finally {
2484
- isRestoring.value = false;
2485
- }
2486
- }
2487
- return {
2488
- snapshots,
2489
- isLoading,
2490
- isRestoring,
2491
- loadSnapshots,
2492
- restoreSnapshot
2493
- };
2494
- }
2495
-
2496
- // src/cloud/test-email.ts
2497
- var import_vue13 = require("vue");
2498
- function useTestEmail(options) {
2499
- const {
2500
- authManager,
2501
- getTemplateId,
2502
- save,
2503
- exportHtml,
2504
- onError,
2505
- isAuthReady,
2506
- onBeforeTestEmail
2507
- } = options;
2508
- const api = new ApiClient(authManager);
2509
- const isSending = (0, import_vue13.ref)(false);
2510
- const error = (0, import_vue13.ref)(null);
2511
- const testEmailConfig = (0, import_vue13.ref)(null);
2512
- if (isAuthReady) {
2513
- (0, import_vue13.watch)(
2514
- isAuthReady,
2515
- (ready) => {
2516
- if (ready) {
2517
- testEmailConfig.value = authManager.testEmailConfig;
2518
- }
2519
- },
2520
- { immediate: true }
2521
- );
2522
- }
2523
- const isEnabled = (0, import_vue13.computed)(() => testEmailConfig.value !== null);
2524
- const allowedEmails = (0, import_vue13.computed)(
2525
- () => testEmailConfig.value?.allowedEmails ?? []
2526
- );
2527
- async function sendTestEmail(recipient) {
2528
- if (!testEmailConfig.value) {
2529
- throw new Error("Test email is not enabled for this project");
2530
- }
2531
- const templateId = getTemplateId();
2532
- if (!templateId) {
2533
- throw new Error("Template must be saved before sending a test email");
2534
- }
2535
- isSending.value = true;
2536
- error.value = null;
2537
- try {
2538
- await save();
2539
- let { html } = await exportHtml(templateId);
2540
- if (onBeforeTestEmail) {
2541
- html = await onBeforeTestEmail(html);
2542
- }
2543
- await api.sendTestEmail(templateId, {
2544
- recipient,
2545
- html,
2546
- allowed_emails: testEmailConfig.value.allowedEmails,
2547
- signature: testEmailConfig.value.signature
2548
- });
2549
- } catch (err) {
2550
- const wrappedError = err instanceof Error ? err : new Error("Failed to send test email", { cause: err });
2551
- error.value = wrappedError.message;
2552
- onError?.(wrappedError);
2553
- throw wrappedError;
2554
- } finally {
2555
- isSending.value = false;
2556
- }
2557
- }
2558
- return {
2559
- isEnabled,
2560
- allowedEmails,
2561
- isSending,
2562
- error,
2563
- sendTestEmail
2564
- };
2565
- }
2566
-
2567
- // src/cloud/export.ts
2568
- function useExport(options) {
2569
- const { authManager, getFontsConfig, canUseCustomFonts } = options;
2570
- const api = new ApiClient(authManager);
2571
- function getExportFontsPayload() {
2572
- const fontsConfig = getFontsConfig?.();
2573
- const customFontsAllowed = canUseCustomFonts?.() ?? true;
2574
- return {
2575
- customFonts: customFontsAllowed && fontsConfig?.customFonts ? fontsConfig.customFonts : [],
2576
- defaultFallback: fontsConfig?.defaultFallback ?? "Arial, sans-serif"
2577
- };
2578
- }
2579
- async function exportHtml(templateId) {
2580
- const fontsPayload = getExportFontsPayload();
2581
- const result = await api.exportTemplate(templateId, fontsPayload);
2582
- return {
2583
- html: result.html,
2584
- mjml: result.mjml
2585
- };
2586
- }
2587
- async function getMjmlSource(templateId) {
2588
- const fontsPayload = getExportFontsPayload();
2589
- const result = await api.exportTemplate(templateId, fontsPayload);
2590
- return result.mjml;
2591
- }
2592
- return {
2593
- exportHtml,
2594
- getMjmlSource
2595
- };
2596
- }
2597
-
2598
- // src/cloud/plan-config.ts
2599
- var import_vue14 = require("vue");
2600
- function usePlanConfig(options) {
2601
- const { authManager, onError } = options;
2602
- const config = (0, import_vue14.ref)(null);
2603
- const isLoading = (0, import_vue14.ref)(false);
2604
- const apiClient = new ApiClient(authManager);
2605
- const features = (0, import_vue14.computed)(() => config.value?.features ?? null);
2606
- function hasFeature(feature) {
2607
- return config.value?.features[feature] ?? false;
2608
- }
2609
- async function fetchConfig() {
2610
- if (isLoading.value) {
2611
- return;
2612
- }
2613
- isLoading.value = true;
2614
- try {
2615
- config.value = await apiClient.fetchConfig();
2616
- } catch (error) {
2617
- onError?.(
2618
- error instanceof Error ? error : new Error("Failed to fetch config", { cause: error })
2619
- );
2620
- } finally {
2621
- isLoading.value = false;
2622
- }
2623
- }
2624
- return {
2625
- config,
2626
- isLoading,
2627
- hasFeature,
2628
- features,
2629
- fetchConfig
2630
- };
2631
- }
2632
-
2633
- // src/cloud/health-check.ts
2634
- var WS_HANDSHAKE_TIMEOUT = 5e3;
2635
- function resolveHealthUrl(options) {
2636
- if (options.authManager) {
2637
- return options.authManager.resolveUrl(API_ROUTES.health);
2638
- }
2639
- const base = (options.baseUrl ?? "https://templatical.com").replace(
2640
- /\/$/,
2641
- ""
2642
- );
2643
- return `${base}${API_ROUTES.health}`;
2644
- }
2645
- async function checkApiAndAuth(url, authManager) {
2646
- const start = performance.now();
2647
- try {
2648
- const response = authManager ? await authManager.authenticatedFetch(API_ROUTES.health, {
2649
- method: "GET",
2650
- headers: { Accept: "application/json" }
2651
- }) : await fetch(url, {
2652
- method: "GET",
2653
- headers: { Accept: "application/json" }
2654
- });
2655
- const latency = Math.round(performance.now() - start);
2656
- if (response.status === 401) {
2657
- return {
2658
- api: { ok: true, latency },
2659
- auth: { ok: false, error: "HTTP 401" }
2660
- };
2661
- }
2662
- if (!response.ok) {
2663
- return {
2664
- api: { ok: false, latency },
2665
- auth: {
2666
- ok: !authManager,
2667
- error: authManager ? `HTTP ${response.status}` : void 0
2668
- }
2669
- };
2670
- }
2671
- const data = await response.json();
2672
- return {
2673
- api: { ok: data.status === "ok", latency },
2674
- auth: { ok: true },
2675
- wsConfig: data.websocket
2676
- };
2677
- } catch (error) {
2678
- const latency = Math.round(performance.now() - start);
2679
- return {
2680
- api: { ok: false, latency },
2681
- auth: {
2682
- ok: !authManager,
2683
- error: authManager ? error instanceof Error ? error.message : "Authentication check failed" : void 0
2684
- }
2685
- };
2686
- }
2687
- }
2688
- async function checkWebSocket(wsConfig) {
2689
- if (!wsConfig?.host || !wsConfig?.app_key) {
2690
- return { ok: false, error: "WebSocket configuration not available" };
2691
- }
2692
- if (typeof WebSocket === "undefined") {
2693
- return {
2694
- ok: false,
2695
- error: "WebSocket not supported in this environment"
2696
- };
2697
- }
2698
- const protocol = wsConfig.port === 443 ? "wss" : "ws";
2699
- const url = `${protocol}://${wsConfig.host}:${wsConfig.port}/app/${wsConfig.app_key}?protocol=7&client=js&version=8.4.0-rc2&flash=false`;
2700
- return new Promise((resolve) => {
2701
- const timeout = setTimeout(() => {
2702
- ws.close();
2703
- resolve({ ok: false, error: "WebSocket connection timed out" });
2704
- }, WS_HANDSHAKE_TIMEOUT);
2705
- const ws = new WebSocket(url);
2706
- ws.onopen = () => {
2707
- clearTimeout(timeout);
2708
- ws.close();
2709
- resolve({ ok: true });
2710
- };
2711
- ws.onerror = () => {
2712
- clearTimeout(timeout);
2713
- resolve({ ok: false, error: "WebSocket connection failed" });
2714
- };
2715
- });
2716
- }
2717
- async function performHealthCheck(options = {}) {
2718
- const healthUrl = resolveHealthUrl(options);
2719
- const result = await checkApiAndAuth(healthUrl, options.authManager);
2720
- const wsResult = await checkWebSocket(result.wsConfig);
2721
- return {
2722
- api: result.api,
2723
- websocket: wsResult,
2724
- auth: result.auth,
2725
- overall: result.api.ok && result.auth.ok
2726
- };
2727
- }
2728
-
2729
- // src/cloud/mcp-listener.ts
2730
- var import_vue15 = require("vue");
2731
- function useMcpListener(options) {
2732
- const { editor, channel, onOperation } = options;
2733
- (0, import_vue15.watch)(channel, (newChannel, oldChannel) => {
2734
- if (oldChannel) {
2735
- oldChannel.unbind("mcp-operation");
2736
- }
2737
- if (newChannel) {
2738
- newChannel.bind("mcp-operation", (payload) => {
2739
- handleOperation(editor, payload);
2740
- onOperation?.(payload);
2741
- });
2742
- }
2743
- });
2744
- }
2745
- // Annotate the CommonJS export names for ESM import in node:
2746
- 0 && (module.exports = {
2747
- API_ROUTES,
2748
- ApiClient,
2749
- AuthManager,
2750
- WebSocketClient,
2751
- buildUrl,
2752
- createSdkAuthManager,
2753
- handleOperation,
2754
- performHealthCheck,
2755
- resolveWebSocketConfig,
2756
- useAiChat,
2757
- useAiConfig,
2758
- useAiRewrite,
2759
- useCollaboration,
2760
- useCollaborationBroadcast,
2761
- useCommentListener,
2762
- useComments,
2763
- useDesignReference,
2764
- useEditor,
2765
- useExport,
2766
- useMcpListener,
2767
- usePlanConfig,
2768
- useSavedModules,
2769
- useSnapshotHistory,
2770
- useTemplateScoring,
2771
- useTestEmail,
2772
- useWebSocket
2773
- });
2774
- //# sourceMappingURL=index.cjs.map