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