dynim-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +290 -0
- package/dist/builder/ai-prompt-popover.d.ts +26 -0
- package/dist/builder/ai-prompt-popover.d.ts.map +1 -0
- package/dist/builder/ai-prompt-popover.js +180 -0
- package/dist/builder/builder-client.d.ts +48 -0
- package/dist/builder/builder-client.d.ts.map +1 -0
- package/dist/builder/builder-client.js +157 -0
- package/dist/builder/builder.d.ts +41 -0
- package/dist/builder/builder.d.ts.map +1 -0
- package/dist/builder/builder.js +537 -0
- package/dist/builder/bundle-manager.d.ts +60 -0
- package/dist/builder/bundle-manager.d.ts.map +1 -0
- package/dist/builder/bundle-manager.js +357 -0
- package/dist/builder/classifier/classname-analyzer.d.ts +6 -0
- package/dist/builder/classifier/classname-analyzer.d.ts.map +1 -0
- package/dist/builder/classifier/classname-analyzer.js +107 -0
- package/dist/builder/classifier/index.d.ts +20 -0
- package/dist/builder/classifier/index.d.ts.map +1 -0
- package/dist/builder/classifier/index.js +181 -0
- package/dist/builder/classifier/semantic-analyzer.d.ts +24 -0
- package/dist/builder/classifier/semantic-analyzer.d.ts.map +1 -0
- package/dist/builder/classifier/semantic-analyzer.js +94 -0
- package/dist/builder/classifier/size-analyzer.d.ts +7 -0
- package/dist/builder/classifier/size-analyzer.d.ts.map +1 -0
- package/dist/builder/classifier/size-analyzer.js +120 -0
- package/dist/builder/classifier/visual-analyzer.d.ts +6 -0
- package/dist/builder/classifier/visual-analyzer.d.ts.map +1 -0
- package/dist/builder/classifier/visual-analyzer.js +158 -0
- package/dist/builder/client.d.ts +22 -0
- package/dist/builder/client.d.ts.map +1 -0
- package/dist/builder/client.js +54 -0
- package/dist/builder/code-client.d.ts +101 -0
- package/dist/builder/code-client.d.ts.map +1 -0
- package/dist/builder/code-client.js +418 -0
- package/dist/builder/diff-state.d.ts +24 -0
- package/dist/builder/diff-state.d.ts.map +1 -0
- package/dist/builder/diff-state.js +134 -0
- package/dist/builder/dom-scanner.d.ts +20 -0
- package/dist/builder/dom-scanner.d.ts.map +1 -0
- package/dist/builder/dom-scanner.js +102 -0
- package/dist/builder/drag-engine.d.ts +41 -0
- package/dist/builder/drag-engine.d.ts.map +1 -0
- package/dist/builder/drag-engine.js +686 -0
- package/dist/builder/editor-overlays.d.ts +31 -0
- package/dist/builder/editor-overlays.d.ts.map +1 -0
- package/dist/builder/editor-overlays.js +202 -0
- package/dist/builder/editor-state.d.ts +50 -0
- package/dist/builder/editor-state.d.ts.map +1 -0
- package/dist/builder/editor-state.js +132 -0
- package/dist/builder/element-utils.d.ts +43 -0
- package/dist/builder/element-utils.d.ts.map +1 -0
- package/dist/builder/element-utils.js +227 -0
- package/dist/builder/fiber-capture.d.ts +28 -0
- package/dist/builder/fiber-capture.d.ts.map +1 -0
- package/dist/builder/fiber-capture.js +264 -0
- package/dist/builder/freeze-overlay.d.ts +26 -0
- package/dist/builder/freeze-overlay.d.ts.map +1 -0
- package/dist/builder/freeze-overlay.js +213 -0
- package/dist/builder/history-state.d.ts +41 -0
- package/dist/builder/history-state.d.ts.map +1 -0
- package/dist/builder/history-state.js +76 -0
- package/dist/builder/index.d.ts +62 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +92 -0
- package/dist/builder/state.d.ts +27 -0
- package/dist/builder/state.d.ts.map +1 -0
- package/dist/builder/state.js +50 -0
- package/dist/builder/style-applier.d.ts +61 -0
- package/dist/builder/style-applier.d.ts.map +1 -0
- package/dist/builder/style-applier.js +311 -0
- package/dist/builder/tree-state.d.ts +71 -0
- package/dist/builder/tree-state.d.ts.map +1 -0
- package/dist/builder/tree-state.js +168 -0
- package/dist/builder/widget.d.ts +29 -0
- package/dist/builder/widget.d.ts.map +1 -0
- package/dist/builder/widget.js +181 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/package.json +25 -0
- package/src/styles/base.css +378 -0
- package/src/styles/builder.css +422 -0
- package/src/styles/editor.css +131 -0
- package/src/styles/themes/dark.css +24 -0
- package/src/styles/themes/light.css +21 -0
- package/src/styles/variables.css +63 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Client - Handles communication with the flexcode endpoints via build server
|
|
3
|
+
*
|
|
4
|
+
* Uses:
|
|
5
|
+
* - HTTP POST + SSE for code generation (streaming responses)
|
|
6
|
+
* - HTTP POST for save/abandon operations
|
|
7
|
+
* - HTTP GET for retrieving edits
|
|
8
|
+
* - JWT session tokens for authentication
|
|
9
|
+
*
|
|
10
|
+
* The SDK only talks to the build server (:8080), which forwards
|
|
11
|
+
* code requests to the flexcode service (:8765).
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_API_BASE = 'http://localhost:8080';
|
|
14
|
+
/** Create initial empty message state */
|
|
15
|
+
function createEmptyMessage() {
|
|
16
|
+
return {
|
|
17
|
+
thinking: '',
|
|
18
|
+
text: '',
|
|
19
|
+
edits: [],
|
|
20
|
+
status: 'idle',
|
|
21
|
+
bundleReady: false,
|
|
22
|
+
bundleError: undefined,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function createCodeClient(config = {}) {
|
|
26
|
+
const { apiBase = DEFAULT_API_BASE, sessionToken: initialSessionToken, refreshToken: initialRefreshToken, getSession, onMessageUpdate, onRawEvent, onMessage, // deprecated but still supported
|
|
27
|
+
onEdit, onError, onStreamStart, onStreamEnd, onSessionRefresh, onAuthError } = config;
|
|
28
|
+
let currentSessionToken = initialSessionToken;
|
|
29
|
+
let currentRefreshToken = initialRefreshToken;
|
|
30
|
+
let isRefreshing = false;
|
|
31
|
+
// Track if cache has been warmed (projectId comes from JWT, so just need one flag)
|
|
32
|
+
let hasWarmedCache = false;
|
|
33
|
+
// Current message state (accumulated)
|
|
34
|
+
let currentMessage = createEmptyMessage();
|
|
35
|
+
// Track if we need to add a line break before next text (after tool use)
|
|
36
|
+
let needsTextBreak = false;
|
|
37
|
+
/** Update message state and notify listeners */
|
|
38
|
+
function updateMessage(updates) {
|
|
39
|
+
currentMessage = { ...currentMessage, ...updates };
|
|
40
|
+
onMessageUpdate?.(currentMessage);
|
|
41
|
+
}
|
|
42
|
+
/** Append to thinking content */
|
|
43
|
+
function appendThinking(content) {
|
|
44
|
+
// If we already have text output and now Claude is thinking again,
|
|
45
|
+
// that means a new response segment is starting - mark for line break
|
|
46
|
+
if (currentMessage.text.length > 0) {
|
|
47
|
+
needsTextBreak = true;
|
|
48
|
+
}
|
|
49
|
+
currentMessage = {
|
|
50
|
+
...currentMessage,
|
|
51
|
+
thinking: currentMessage.thinking + content
|
|
52
|
+
};
|
|
53
|
+
onMessageUpdate?.(currentMessage);
|
|
54
|
+
}
|
|
55
|
+
/** Append to text content */
|
|
56
|
+
function appendText(content) {
|
|
57
|
+
// Add line break if this is new text after a tool use
|
|
58
|
+
let prefix = '';
|
|
59
|
+
if (needsTextBreak && currentMessage.text.length > 0) {
|
|
60
|
+
prefix = '\n\n';
|
|
61
|
+
needsTextBreak = false;
|
|
62
|
+
}
|
|
63
|
+
currentMessage = {
|
|
64
|
+
...currentMessage,
|
|
65
|
+
text: currentMessage.text + prefix + content
|
|
66
|
+
};
|
|
67
|
+
onMessageUpdate?.(currentMessage);
|
|
68
|
+
}
|
|
69
|
+
/** Mark that next text should have a line break (called after tool use) */
|
|
70
|
+
function markTextBreak() {
|
|
71
|
+
needsTextBreak = true;
|
|
72
|
+
}
|
|
73
|
+
/** Add an edit */
|
|
74
|
+
function addEdit(edit) {
|
|
75
|
+
currentMessage = {
|
|
76
|
+
...currentMessage,
|
|
77
|
+
edits: [...currentMessage.edits, edit]
|
|
78
|
+
};
|
|
79
|
+
onMessageUpdate?.(currentMessage);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get a valid session token, refreshing if necessary
|
|
83
|
+
*/
|
|
84
|
+
async function getValidToken() {
|
|
85
|
+
if (currentSessionToken) {
|
|
86
|
+
// Check if token is expired (JWT decode)
|
|
87
|
+
try {
|
|
88
|
+
const payload = JSON.parse(atob(currentSessionToken.split('.')[1]));
|
|
89
|
+
const exp = payload.exp * 1000; // Convert to ms
|
|
90
|
+
if (Date.now() < exp - 60000) { // 1 minute buffer
|
|
91
|
+
return currentSessionToken;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Token parse failed, try to refresh
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Try to refresh
|
|
99
|
+
if (currentRefreshToken && !isRefreshing) {
|
|
100
|
+
isRefreshing = true;
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(`${apiBase}/api/sessions/refresh`, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({ refresh_token: currentRefreshToken })
|
|
106
|
+
});
|
|
107
|
+
if (response.ok) {
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
currentSessionToken = data.token;
|
|
110
|
+
onSessionRefresh?.(data.token);
|
|
111
|
+
isRefreshing = false;
|
|
112
|
+
return currentSessionToken ?? null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error('[CodeClient] Failed to refresh session:', err);
|
|
117
|
+
}
|
|
118
|
+
isRefreshing = false;
|
|
119
|
+
}
|
|
120
|
+
// Try to get new session from callback
|
|
121
|
+
if (getSession) {
|
|
122
|
+
try {
|
|
123
|
+
const session = await getSession();
|
|
124
|
+
currentSessionToken = session.token;
|
|
125
|
+
if (session.refreshToken) {
|
|
126
|
+
currentRefreshToken = session.refreshToken;
|
|
127
|
+
}
|
|
128
|
+
return currentSessionToken;
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
console.error('[CodeClient] Failed to get session:', err);
|
|
132
|
+
onAuthError?.(err);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
onAuthError?.(new Error('No valid session token available'));
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Make an authenticated request
|
|
140
|
+
*/
|
|
141
|
+
async function authenticatedFetch(url, options = {}) {
|
|
142
|
+
const token = await getValidToken();
|
|
143
|
+
if (!token) {
|
|
144
|
+
throw new Error('Not authenticated');
|
|
145
|
+
}
|
|
146
|
+
const headers = new Headers(options.headers);
|
|
147
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
148
|
+
return fetch(url, { ...options, headers });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Send a code generation request and receive SSE streaming response
|
|
152
|
+
* ProjectId comes from the JWT token (set when session was created)
|
|
153
|
+
*/
|
|
154
|
+
async function sendCode(query) {
|
|
155
|
+
const token = await getValidToken();
|
|
156
|
+
if (!token) {
|
|
157
|
+
onAuthError?.(new Error('Not authenticated'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Reset message state for new request
|
|
161
|
+
currentMessage = createEmptyMessage();
|
|
162
|
+
needsTextBreak = false;
|
|
163
|
+
updateMessage({ status: 'streaming' });
|
|
164
|
+
// Auto-warm cache (fire and forget)
|
|
165
|
+
if (!hasWarmedCache) {
|
|
166
|
+
hasWarmedCache = true;
|
|
167
|
+
warmCache().catch(() => { });
|
|
168
|
+
}
|
|
169
|
+
const body = JSON.stringify({ query });
|
|
170
|
+
console.log('[CodeClient] Sending code request:', { query: query.slice(0, 50) });
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch(`${apiBase}/api/code`, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: {
|
|
175
|
+
'Authorization': `Bearer ${token}`,
|
|
176
|
+
'Content-Type': 'application/json',
|
|
177
|
+
'Accept': 'text/event-stream'
|
|
178
|
+
},
|
|
179
|
+
body
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
if (response.status === 401 || response.status === 403) {
|
|
183
|
+
onAuthError?.(new Error('Authentication failed'));
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
186
|
+
}
|
|
187
|
+
onStreamStart?.();
|
|
188
|
+
try {
|
|
189
|
+
// Read SSE stream
|
|
190
|
+
const reader = response.body?.getReader();
|
|
191
|
+
if (!reader) {
|
|
192
|
+
throw new Error('No response body');
|
|
193
|
+
}
|
|
194
|
+
const decoder = new TextDecoder();
|
|
195
|
+
let buffer = '';
|
|
196
|
+
while (true) {
|
|
197
|
+
const { done, value } = await reader.read();
|
|
198
|
+
if (done)
|
|
199
|
+
break;
|
|
200
|
+
buffer += decoder.decode(value, { stream: true });
|
|
201
|
+
// Process complete SSE events
|
|
202
|
+
const lines = buffer.split('\n');
|
|
203
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
if (line.startsWith('data: ')) {
|
|
206
|
+
try {
|
|
207
|
+
const data = JSON.parse(line.slice(6));
|
|
208
|
+
// Skip keepalive events (just for connection health)
|
|
209
|
+
if (data.type === 'keepalive') {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
// Notify raw event listeners
|
|
213
|
+
onRawEvent?.(data);
|
|
214
|
+
onMessage?.(data); // deprecated callback
|
|
215
|
+
// Process event and update structured message
|
|
216
|
+
switch (data.type) {
|
|
217
|
+
case 'thinking':
|
|
218
|
+
if (data.content) {
|
|
219
|
+
appendThinking(data.content);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
case 'text':
|
|
223
|
+
if (data.content) {
|
|
224
|
+
appendText(data.content);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'edit':
|
|
228
|
+
if (data.edit) {
|
|
229
|
+
addEdit(data.edit);
|
|
230
|
+
onEdit?.(data.edit);
|
|
231
|
+
// Next text after an edit should be on a new line
|
|
232
|
+
markTextBreak();
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
case 'question':
|
|
236
|
+
updateMessage({
|
|
237
|
+
status: 'waiting',
|
|
238
|
+
question: data.content
|
|
239
|
+
});
|
|
240
|
+
break;
|
|
241
|
+
case 'error':
|
|
242
|
+
updateMessage({
|
|
243
|
+
status: 'error',
|
|
244
|
+
error: data.content
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
case 'session_started':
|
|
248
|
+
updateMessage({ sessionId: data.session_id });
|
|
249
|
+
break;
|
|
250
|
+
case 'done':
|
|
251
|
+
if (currentMessage.status !== 'waiting' && currentMessage.status !== 'error') {
|
|
252
|
+
updateMessage({ status: 'done' });
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case 'bundle_ready':
|
|
256
|
+
console.log('[CodeClient] Bundle ready:', { projectId: data.project_id });
|
|
257
|
+
updateMessage({ bundleReady: true, projectId: data.project_id });
|
|
258
|
+
break;
|
|
259
|
+
case 'bundle_error':
|
|
260
|
+
console.log('[CodeClient] Bundle error:', { error: data.content });
|
|
261
|
+
updateMessage({ bundleError: data.content });
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Ignore parse errors for malformed events
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Ensure we mark as done if stream ends without done event
|
|
272
|
+
if (currentMessage.status === 'streaming') {
|
|
273
|
+
updateMessage({ status: 'done' });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
onStreamEnd?.();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.error('[CodeClient] Code request error:', error);
|
|
282
|
+
updateMessage({ status: 'error', error: error.message });
|
|
283
|
+
onError?.(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Save pending edits (commit temp changes)
|
|
288
|
+
* ProjectId comes from the JWT token
|
|
289
|
+
*/
|
|
290
|
+
async function saveCode() {
|
|
291
|
+
console.log('[CodeClient] Saving code');
|
|
292
|
+
try {
|
|
293
|
+
const response = await authenticatedFetch(`${apiBase}/api/code/save`, {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: { 'Content-Type': 'application/json' },
|
|
296
|
+
body: '{}'
|
|
297
|
+
});
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
if (response.status === 401 || response.status === 403) {
|
|
300
|
+
onAuthError?.(new Error('Authentication failed'));
|
|
301
|
+
}
|
|
302
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
303
|
+
}
|
|
304
|
+
// Reset message state after save
|
|
305
|
+
currentMessage = createEmptyMessage();
|
|
306
|
+
onMessageUpdate?.(currentMessage);
|
|
307
|
+
console.log('[CodeClient] Code saved successfully');
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error('[CodeClient] Save error:', error);
|
|
311
|
+
onError?.(error);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Abandon pending edits (discard temp changes)
|
|
317
|
+
* ProjectId comes from the JWT token
|
|
318
|
+
*/
|
|
319
|
+
async function abandonCode() {
|
|
320
|
+
console.log('[CodeClient] Abandoning code');
|
|
321
|
+
try {
|
|
322
|
+
const response = await authenticatedFetch(`${apiBase}/api/code/abandon`, {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: { 'Content-Type': 'application/json' },
|
|
325
|
+
body: '{}'
|
|
326
|
+
});
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
if (response.status === 401 || response.status === 403) {
|
|
329
|
+
onAuthError?.(new Error('Authentication failed'));
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
332
|
+
}
|
|
333
|
+
// Reset message state after abandon
|
|
334
|
+
currentMessage = createEmptyMessage();
|
|
335
|
+
onMessageUpdate?.(currentMessage);
|
|
336
|
+
console.log('[CodeClient] Code abandoned successfully');
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
console.error('[CodeClient] Abandon error:', error);
|
|
340
|
+
onError?.(error);
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get list of current edits
|
|
346
|
+
* ProjectId comes from the JWT token
|
|
347
|
+
*/
|
|
348
|
+
async function getEdits() {
|
|
349
|
+
console.log('[CodeClient] Getting edits');
|
|
350
|
+
// Proactively warm cache when fetching edits (common init pattern)
|
|
351
|
+
if (!hasWarmedCache) {
|
|
352
|
+
hasWarmedCache = true;
|
|
353
|
+
warmCache().catch(() => { });
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const response = await authenticatedFetch(`${apiBase}/api/code/edits`);
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
if (response.status === 401 || response.status === 403) {
|
|
359
|
+
onAuthError?.(new Error('Authentication failed'));
|
|
360
|
+
}
|
|
361
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
362
|
+
}
|
|
363
|
+
const data = await response.json();
|
|
364
|
+
console.log('[CodeClient] Got edits:', data);
|
|
365
|
+
return data.edits || [];
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error('[CodeClient] Get edits error:', error);
|
|
369
|
+
onError?.(error);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Pre-warm the file cache for faster code requests
|
|
375
|
+
* ProjectId comes from the JWT token
|
|
376
|
+
*/
|
|
377
|
+
async function warmCache() {
|
|
378
|
+
try {
|
|
379
|
+
const response = await authenticatedFetch(`${apiBase}/api/code/warm-cache`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
headers: { 'Content-Type': 'application/json' },
|
|
382
|
+
body: '{}'
|
|
383
|
+
});
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
console.warn('[CodeClient] Warm cache request failed:', response.status);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
console.log('[CodeClient] Cache warming started');
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
console.warn('[CodeClient] Warm cache error:', error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Set/update the session token
|
|
396
|
+
*/
|
|
397
|
+
function setSessionToken(token, refreshToken) {
|
|
398
|
+
currentSessionToken = token;
|
|
399
|
+
if (refreshToken) {
|
|
400
|
+
currentRefreshToken = refreshToken;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
sendCode,
|
|
405
|
+
saveCode,
|
|
406
|
+
abandonCode,
|
|
407
|
+
getEdits,
|
|
408
|
+
warmCache,
|
|
409
|
+
setSessionToken,
|
|
410
|
+
isAuthenticated: () => !!currentSessionToken,
|
|
411
|
+
getMessage: () => currentMessage,
|
|
412
|
+
resetMessage: () => {
|
|
413
|
+
currentMessage = createEmptyMessage();
|
|
414
|
+
needsTextBreak = false;
|
|
415
|
+
onMessageUpdate?.(currentMessage);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff State - Tracks original element positions for computing serializable diffs
|
|
3
|
+
*
|
|
4
|
+
* This module records the FIRST position of each moved element.
|
|
5
|
+
* When getDiff() is called, it compares original vs current DOM state
|
|
6
|
+
* to produce serializable diff objects (not DOM references).
|
|
7
|
+
*/
|
|
8
|
+
import { type ElementIdentifier, type PlacementTarget } from './element-utils';
|
|
9
|
+
export interface DiffEntry {
|
|
10
|
+
element: ElementIdentifier;
|
|
11
|
+
type: 'moved' | 'removed';
|
|
12
|
+
from: PlacementTarget;
|
|
13
|
+
to: PlacementTarget;
|
|
14
|
+
}
|
|
15
|
+
export interface DiffState {
|
|
16
|
+
snapshotInitialState: (rootElement: HTMLElement) => void;
|
|
17
|
+
trackElement: (domElement: HTMLElement, fallbackPlacement?: PlacementTarget) => void;
|
|
18
|
+
getDiff: () => DiffEntry[];
|
|
19
|
+
hasChanges: () => boolean;
|
|
20
|
+
clearAll: () => void;
|
|
21
|
+
getTrackedCount: () => number;
|
|
22
|
+
}
|
|
23
|
+
export declare function createDiffState(): DiffState;
|
|
24
|
+
//# sourceMappingURL=diff-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-state.d.ts","sourceRoot":"","sources":["../../src/builder/diff-state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,EAAE,EAAE,eAAe,CAAC;CACrB;AA6CD,MAAM,WAAW,SAAS;IACxB,oBAAoB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IACzD,YAAY,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACrF,OAAO,EAAE,MAAM,SAAS,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED,wBAAgB,eAAe,IAAI,SAAS,CAyG3C"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff State - Tracks original element positions for computing serializable diffs
|
|
3
|
+
*
|
|
4
|
+
* This module records the FIRST position of each moved element.
|
|
5
|
+
* When getDiff() is called, it compares original vs current DOM state
|
|
6
|
+
* to produce serializable diff objects (not DOM references).
|
|
7
|
+
*/
|
|
8
|
+
import { getStableId, findByStableId, buildElementIdentifier, buildPlacementTarget } from './element-utils';
|
|
9
|
+
function serializeIdentifier(el) {
|
|
10
|
+
if (!el)
|
|
11
|
+
return '';
|
|
12
|
+
if (el.id)
|
|
13
|
+
return `#${el.id}`;
|
|
14
|
+
const parts = [
|
|
15
|
+
el.tagName,
|
|
16
|
+
el.classes.sort().join('.'),
|
|
17
|
+
`nth:${el.nthOfType}`,
|
|
18
|
+
el.textContentPreview?.slice(0, 20) || '',
|
|
19
|
+
];
|
|
20
|
+
if (el.parentIdentifier) {
|
|
21
|
+
parts.push('>' + serializeIdentifier(el.parentIdentifier));
|
|
22
|
+
}
|
|
23
|
+
return parts.join('|');
|
|
24
|
+
}
|
|
25
|
+
function placementsEqual(a, b) {
|
|
26
|
+
if (!a || !b)
|
|
27
|
+
return a === b;
|
|
28
|
+
if (a.position !== b.position)
|
|
29
|
+
return false;
|
|
30
|
+
if (a.parentStableId && b.parentStableId) {
|
|
31
|
+
if (a.parentStableId !== b.parentStableId)
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (serializeIdentifier(a.parent) !== serializeIdentifier(b.parent))
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (a.referenceStableId && b.referenceStableId) {
|
|
39
|
+
return a.referenceStableId === b.referenceStableId;
|
|
40
|
+
}
|
|
41
|
+
if (a.referenceElement && b.referenceElement) {
|
|
42
|
+
return serializeIdentifier(a.referenceElement) === serializeIdentifier(b.referenceElement);
|
|
43
|
+
}
|
|
44
|
+
return a.referenceElement === b.referenceElement;
|
|
45
|
+
}
|
|
46
|
+
export function createDiffState() {
|
|
47
|
+
const initialSnapshot = new Map();
|
|
48
|
+
const movedElements = new Map();
|
|
49
|
+
function snapshotInitialState(rootElement) {
|
|
50
|
+
if (!rootElement)
|
|
51
|
+
return;
|
|
52
|
+
function walkAndSnapshot(element) {
|
|
53
|
+
if (element.nodeType !== Node.ELEMENT_NODE)
|
|
54
|
+
return;
|
|
55
|
+
const htmlElement = element;
|
|
56
|
+
const stableId = getStableId(htmlElement);
|
|
57
|
+
const placement = buildPlacementTarget(htmlElement);
|
|
58
|
+
if (placement) {
|
|
59
|
+
initialSnapshot.set(stableId, placement);
|
|
60
|
+
}
|
|
61
|
+
for (const child of Array.from(element.children)) {
|
|
62
|
+
walkAndSnapshot(child);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const child of Array.from(rootElement.children)) {
|
|
66
|
+
walkAndSnapshot(child);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function trackElement(domElement, _fallbackPlacement) {
|
|
70
|
+
if (!domElement)
|
|
71
|
+
return;
|
|
72
|
+
const stableId = getStableId(domElement);
|
|
73
|
+
if (movedElements.has(stableId)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const elementIdentifier = buildElementIdentifier(domElement);
|
|
77
|
+
movedElements.set(stableId, {
|
|
78
|
+
stableId,
|
|
79
|
+
element: elementIdentifier,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getDiff() {
|
|
83
|
+
const diffs = [];
|
|
84
|
+
for (const [stableId, tracked] of movedElements) {
|
|
85
|
+
const element = findByStableId(stableId);
|
|
86
|
+
const originalPlacement = initialSnapshot.get(stableId);
|
|
87
|
+
if (!element) {
|
|
88
|
+
if (originalPlacement) {
|
|
89
|
+
diffs.push({
|
|
90
|
+
element: tracked.element,
|
|
91
|
+
type: 'removed',
|
|
92
|
+
from: originalPlacement,
|
|
93
|
+
to: originalPlacement,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!originalPlacement)
|
|
99
|
+
continue;
|
|
100
|
+
const currentPlacement = buildPlacementTarget(element);
|
|
101
|
+
if (!currentPlacement)
|
|
102
|
+
continue;
|
|
103
|
+
if (!placementsEqual(originalPlacement, currentPlacement)) {
|
|
104
|
+
diffs.push({
|
|
105
|
+
element: tracked.element,
|
|
106
|
+
type: 'moved',
|
|
107
|
+
from: originalPlacement,
|
|
108
|
+
to: currentPlacement,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return diffs;
|
|
113
|
+
}
|
|
114
|
+
function hasChanges() {
|
|
115
|
+
return movedElements.size > 0;
|
|
116
|
+
}
|
|
117
|
+
function clearAll() {
|
|
118
|
+
movedElements.clear();
|
|
119
|
+
}
|
|
120
|
+
function getTrackedCount() {
|
|
121
|
+
return movedElements.size;
|
|
122
|
+
}
|
|
123
|
+
if (typeof window !== 'undefined') {
|
|
124
|
+
window.__diffState = { getDiff, hasChanges, getTrackedCount, movedElements, initialSnapshot };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
snapshotInitialState,
|
|
128
|
+
trackElement,
|
|
129
|
+
getDiff,
|
|
130
|
+
hasChanges,
|
|
131
|
+
clearAll,
|
|
132
|
+
getTrackedCount,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM scanner - serializes DOM tree for the component tree
|
|
3
|
+
*/
|
|
4
|
+
import { STABLE_ID_ATTR } from './element-utils';
|
|
5
|
+
import type { SerializedDOMNode } from './tree-state';
|
|
6
|
+
export interface ScanOptions {
|
|
7
|
+
maxDepth?: number;
|
|
8
|
+
skipIds?: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function scanDOM(root: HTMLElement, options?: ScanOptions): SerializedDOMNode | null;
|
|
11
|
+
export interface DebouncedScanner {
|
|
12
|
+
scan: () => void;
|
|
13
|
+
cancel: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function createDebouncedScanner(root: HTMLElement, callback: (tree: SerializedDOMNode | null) => void, delay?: number): DebouncedScanner;
|
|
16
|
+
export declare function getElementAtPath(path: number[], root: HTMLElement): HTMLElement | null;
|
|
17
|
+
export declare function queryElements(selector: string, root?: HTMLElement): HTMLElement[];
|
|
18
|
+
export declare function generateSelector(element: HTMLElement): string;
|
|
19
|
+
export { STABLE_ID_ATTR };
|
|
20
|
+
//# sourceMappingURL=dom-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-scanner.d.ts","sourceRoot":"","sources":["../../src/builder/dom-scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAgD,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,WAAgB,GAAG,iBAAiB,GAAG,IAAI,CAiD9F;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,EAClD,KAAK,SAAM,GACV,gBAAgB,CAqBlB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAStF;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,WAA2B,GAAG,WAAW,EAAE,CAMhG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAiB7D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|