lisichatbot 1.2.1 → 1.2.4
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/package.json +1 -1
- package/src/index.js +223 -22
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -16,7 +16,8 @@ let chatState = {
|
|
|
16
16
|
data: {},
|
|
17
17
|
history: [],
|
|
18
18
|
currentSelection: null,
|
|
19
|
-
returnToStep: null // Track where to return after editing
|
|
19
|
+
returnToStep: null, // Track where to return after editing
|
|
20
|
+
completed: false // Track if flow is completed
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
let elements = {
|
|
@@ -89,6 +90,17 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
89
90
|
// Add step number if provided
|
|
90
91
|
if (stepNumber !== null) {
|
|
91
92
|
clone.setAttribute('data-chat-step', stepNumber);
|
|
93
|
+
|
|
94
|
+
// Mark all previous instances of this step as NOT latest
|
|
95
|
+
const previousInstances = elements.messages.querySelectorAll(
|
|
96
|
+
`[data-chat-element="${type}-message-wrapper"][data-chat-step="${stepNumber}"]`
|
|
97
|
+
);
|
|
98
|
+
previousInstances.forEach(prev => {
|
|
99
|
+
prev.removeAttribute('data-chat-latest');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Mark this as the latest instance of this step
|
|
103
|
+
clone.setAttribute('data-chat-latest', 'true');
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
// Find text element in clone and set content
|
|
@@ -111,8 +123,11 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
111
123
|
editIcon.style.setProperty('display', 'none', 'important');
|
|
112
124
|
console.log(' ✓ Edit icon hidden (display: none !important)');
|
|
113
125
|
|
|
114
|
-
//
|
|
115
|
-
|
|
126
|
+
// Check if this is the latest instance (it should be since we just marked it)
|
|
127
|
+
const isLatest = clone.hasAttribute('data-chat-latest');
|
|
128
|
+
|
|
129
|
+
// Only show if this step has input AND it's a PREVIOUS step AND it's the latest instance
|
|
130
|
+
if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
|
|
116
131
|
editIcon.setAttribute('data-chat-step', stepNumber);
|
|
117
132
|
editIcon.onclick = (e) => {
|
|
118
133
|
e.stopPropagation();
|
|
@@ -125,9 +140,11 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
125
140
|
editIcon.style.setProperty('display', 'block', 'important');
|
|
126
141
|
editIcon.style.setProperty('margin-left', '8px', 'important');
|
|
127
142
|
editIcon.style.setProperty('cursor', 'pointer', 'important');
|
|
128
|
-
console.log(` ✏️ Edit icon SHOWN (step ${stepNumber} is previous step with input)`);
|
|
143
|
+
console.log(` ✏️ Edit icon SHOWN (step ${stepNumber} is latest previous step with input)`);
|
|
129
144
|
} else if (hasInput && stepNumber === chatState.step) {
|
|
130
145
|
console.log(` ⏸️ Edit icon HIDDEN (step ${stepNumber} is CURRENT step)`);
|
|
146
|
+
} else if (!isLatest && hasInput && stepNumber < chatState.step) {
|
|
147
|
+
console.log(` 📜 Edit icon HIDDEN (step ${stepNumber} is OLD instance)`);
|
|
131
148
|
} else {
|
|
132
149
|
console.log(` ⚪ Edit icon HIDDEN (step ${stepNumber || 'N/A'} has no input)`);
|
|
133
150
|
}
|
|
@@ -155,8 +172,10 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
155
172
|
editIconAfterAppend.style.setProperty('display', 'none', 'important');
|
|
156
173
|
editIconAfterAppend.style.setProperty('margin-left', '8px', 'important');
|
|
157
174
|
|
|
158
|
-
|
|
159
|
-
|
|
175
|
+
const isLatest = clone.hasAttribute('data-chat-latest');
|
|
176
|
+
|
|
177
|
+
// Only show if has input AND it's a previous step AND it's latest instance
|
|
178
|
+
if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
|
|
160
179
|
editIconAfterAppend.style.setProperty('display', 'block', 'important');
|
|
161
180
|
|
|
162
181
|
// Debug: Check spacing
|
|
@@ -180,19 +199,27 @@ function updateEditIcons() {
|
|
|
180
199
|
// Find all rendered bot message wrappers
|
|
181
200
|
const allBotMessages = elements.messages.querySelectorAll('[data-chat-element="bot-message-wrapper"]');
|
|
182
201
|
|
|
183
|
-
console.log(`\n🔄 Updating edit icons - Current step: ${chatState.step}`);
|
|
202
|
+
console.log(`\n🔄 Updating edit icons - Current step: ${chatState.step}, Completed: ${chatState.completed}`);
|
|
184
203
|
|
|
185
204
|
allBotMessages.forEach(wrapper => {
|
|
186
205
|
const stepNumber = parseInt(wrapper.getAttribute('data-chat-step'));
|
|
187
206
|
const editIcon = wrapper.querySelector('[data-chat-element="bot-edit-icon"]');
|
|
207
|
+
const isLatest = wrapper.hasAttribute('data-chat-latest');
|
|
188
208
|
|
|
189
209
|
if (editIcon && !isNaN(stepNumber)) {
|
|
190
210
|
// Check if this step has input by looking at the flow
|
|
191
211
|
const stepData = flowData.flow[stepNumber];
|
|
192
212
|
const hasInput = stepData && !!stepData.input;
|
|
193
213
|
|
|
194
|
-
//
|
|
195
|
-
if (
|
|
214
|
+
// Hide ALL icons if flow is completed
|
|
215
|
+
if (chatState.completed) {
|
|
216
|
+
editIcon.style.setProperty('display', 'none', 'important');
|
|
217
|
+
console.log(` 🏁 Step ${stepNumber}: Icon HIDDEN (flow completed)`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Only show icon if: has input AND is previous step AND is latest instance
|
|
222
|
+
if (hasInput && stepNumber < chatState.step && isLatest) {
|
|
196
223
|
// Set onclick handler
|
|
197
224
|
editIcon.onclick = (e) => {
|
|
198
225
|
e.stopPropagation();
|
|
@@ -206,10 +233,12 @@ function updateEditIcons() {
|
|
|
206
233
|
editIcon.style.setProperty('display', 'block', 'important');
|
|
207
234
|
editIcon.style.setProperty('cursor', 'pointer', 'important');
|
|
208
235
|
editIcon.style.setProperty('margin-left', '8px', 'important');
|
|
209
|
-
console.log(` ✏️ Step ${stepNumber}: Icon SHOWN (previous step)`);
|
|
236
|
+
console.log(` ✏️ Step ${stepNumber}: Icon SHOWN (latest previous step)`);
|
|
210
237
|
} else {
|
|
211
238
|
editIcon.style.setProperty('display', 'none', 'important');
|
|
212
|
-
if (stepNumber
|
|
239
|
+
if (!isLatest && hasInput && stepNumber < chatState.step) {
|
|
240
|
+
console.log(` 📜 Step ${stepNumber}: Icon HIDDEN (old instance)`);
|
|
241
|
+
} else if (stepNumber === chatState.step) {
|
|
213
242
|
console.log(` ⏸️ Step ${stepNumber}: Icon HIDDEN (current step)`);
|
|
214
243
|
} else if (!hasInput) {
|
|
215
244
|
console.log(` ⚪ Step ${stepNumber}: Icon HIDDEN (no input)`);
|
|
@@ -238,6 +267,10 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
238
267
|
return;
|
|
239
268
|
}
|
|
240
269
|
|
|
270
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
271
|
+
const existingData = chatState.data[field];
|
|
272
|
+
console.log(`📝 Pre-filling ${field}:`, existingData);
|
|
273
|
+
|
|
241
274
|
// Create wrapper to hold all options
|
|
242
275
|
const optionsWrapper = document.createElement('div');
|
|
243
276
|
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
@@ -255,9 +288,27 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
255
288
|
// Make clone visible (remove display:none from template)
|
|
256
289
|
clone.style.display = '';
|
|
257
290
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
// Check if this option should be pre-selected
|
|
292
|
+
let shouldBeChecked = false;
|
|
293
|
+
if (isSingleSelect) {
|
|
294
|
+
// For single-select, check if value matches existing data
|
|
295
|
+
shouldBeChecked = existingData !== undefined &&
|
|
296
|
+
JSON.stringify(existingData) === JSON.stringify(optionValue);
|
|
297
|
+
} else {
|
|
298
|
+
// For multi-select, check if value is in array
|
|
299
|
+
shouldBeChecked = Array.isArray(existingData) &&
|
|
300
|
+
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Set checked state
|
|
304
|
+
if (shouldBeChecked) {
|
|
305
|
+
clone.classList.add('cf-checked');
|
|
306
|
+
clone.style.backgroundColor = config.selectedBackground;
|
|
307
|
+
console.log(` ✅ Pre-selected: ${optionName}`);
|
|
308
|
+
} else {
|
|
309
|
+
clone.classList.remove('cf-checked');
|
|
310
|
+
clone.style.backgroundColor = 'transparent';
|
|
311
|
+
}
|
|
261
312
|
|
|
262
313
|
// Set data attributes on container
|
|
263
314
|
clone.setAttribute('data-chat-element', inputTypeAttr);
|
|
@@ -272,14 +323,14 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
272
323
|
input.setAttribute('data-chat-element', inputTypeAttr);
|
|
273
324
|
input.name = field;
|
|
274
325
|
input.value = valueStr;
|
|
275
|
-
input.checked =
|
|
326
|
+
input.checked = shouldBeChecked; // Pre-check if needed
|
|
276
327
|
input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
|
|
277
328
|
}
|
|
278
329
|
|
|
279
|
-
// Find and
|
|
330
|
+
// Find and set tick icon visibility
|
|
280
331
|
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
281
332
|
if (tickIcon) {
|
|
282
|
-
tickIcon.style.display = 'none';
|
|
333
|
+
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
283
334
|
}
|
|
284
335
|
|
|
285
336
|
// Find and set text
|
|
@@ -314,6 +365,124 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
314
365
|
});
|
|
315
366
|
}
|
|
316
367
|
|
|
368
|
+
// =============================================================================
|
|
369
|
+
// MULTI-SELECT-COLOR OPTIONS RENDERING
|
|
370
|
+
// =============================================================================
|
|
371
|
+
|
|
372
|
+
function renderColorOptions(options, field) {
|
|
373
|
+
if (!elements.messages) return;
|
|
374
|
+
|
|
375
|
+
// Find existing color option element in HTML by data-chat-element
|
|
376
|
+
const optionSelector = '[data-chat-element="multi-select-color"]';
|
|
377
|
+
const existingOption = document.querySelector(optionSelector);
|
|
378
|
+
|
|
379
|
+
if (!existingOption) {
|
|
380
|
+
console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
385
|
+
const existingData = chatState.data[field];
|
|
386
|
+
console.log(`📝 Pre-filling ${field} (color):`, existingData);
|
|
387
|
+
|
|
388
|
+
// Create wrapper to hold all options
|
|
389
|
+
const optionsWrapper = document.createElement('div');
|
|
390
|
+
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
391
|
+
|
|
392
|
+
// Clone and fill option element for each option
|
|
393
|
+
options.forEach((option, index) => {
|
|
394
|
+
const optionName = option.name || option;
|
|
395
|
+
const optionValue = option.value !== undefined ? option.value : option;
|
|
396
|
+
const optionColor = option.color || '#cccccc'; // Default gray if no color
|
|
397
|
+
const valueStr = typeof optionValue === 'object' ?
|
|
398
|
+
JSON.stringify(optionValue) : String(optionValue);
|
|
399
|
+
|
|
400
|
+
// Clone existing option element
|
|
401
|
+
const clone = existingOption.cloneNode(true);
|
|
402
|
+
|
|
403
|
+
// Make clone visible (remove display:none from template)
|
|
404
|
+
clone.style.display = '';
|
|
405
|
+
|
|
406
|
+
// Check if this option should be pre-selected (multi-select behavior)
|
|
407
|
+
const shouldBeChecked = Array.isArray(existingData) &&
|
|
408
|
+
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
409
|
+
|
|
410
|
+
// Set checked state
|
|
411
|
+
if (shouldBeChecked) {
|
|
412
|
+
clone.classList.add('cf-checked');
|
|
413
|
+
clone.style.backgroundColor = config.selectedBackground;
|
|
414
|
+
console.log(` ✅ Pre-selected color: ${optionName}`);
|
|
415
|
+
} else {
|
|
416
|
+
clone.classList.remove('cf-checked');
|
|
417
|
+
clone.style.backgroundColor = 'transparent';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Set data attributes on container
|
|
421
|
+
clone.setAttribute('data-chat-element', 'multi-select-color');
|
|
422
|
+
clone.setAttribute('data-field', field);
|
|
423
|
+
clone.setAttribute('data-value', valueStr);
|
|
424
|
+
clone.setAttribute('data-name', optionName);
|
|
425
|
+
clone.setAttribute('data-index', index);
|
|
426
|
+
clone.setAttribute('data-color', optionColor);
|
|
427
|
+
|
|
428
|
+
// Find and set input
|
|
429
|
+
const input = clone.querySelector('[data-chat-input-element="input"]');
|
|
430
|
+
if (input) {
|
|
431
|
+
input.setAttribute('data-chat-element', 'multi-select-color');
|
|
432
|
+
input.name = field;
|
|
433
|
+
input.value = valueStr;
|
|
434
|
+
input.checked = shouldBeChecked; // Pre-check if needed
|
|
435
|
+
input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Find and set tick icon visibility
|
|
439
|
+
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
440
|
+
if (tickIcon) {
|
|
441
|
+
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Find and set color block
|
|
445
|
+
const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
|
|
446
|
+
if (colorBlock) {
|
|
447
|
+
colorBlock.style.backgroundColor = optionColor;
|
|
448
|
+
colorBlock.style.display = ''; // Make sure it's visible
|
|
449
|
+
} else {
|
|
450
|
+
console.warn('Color block element not found in multi-select-color template');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Find and set text
|
|
454
|
+
const textElement = clone.querySelector('[data-chat-input-element="text"]');
|
|
455
|
+
if (textElement) {
|
|
456
|
+
textElement.textContent = optionName;
|
|
457
|
+
textElement.style.display = ''; // Make sure text is visible
|
|
458
|
+
} else {
|
|
459
|
+
console.error('Text element not found in option');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Append to wrapper
|
|
463
|
+
optionsWrapper.appendChild(clone);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Append wrapper to messages
|
|
467
|
+
elements.messages.appendChild(optionsWrapper);
|
|
468
|
+
scrollToBottom();
|
|
469
|
+
|
|
470
|
+
// Add click handlers with proper event handling
|
|
471
|
+
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
472
|
+
optionElements.forEach(el => {
|
|
473
|
+
// Clear any existing onclick
|
|
474
|
+
el.onclick = null;
|
|
475
|
+
|
|
476
|
+
// Add new click handler with stopPropagation
|
|
477
|
+
el.onclick = (e) => {
|
|
478
|
+
e.stopPropagation(); // Prevent bubbling
|
|
479
|
+
e.preventDefault(); // Prevent default behavior
|
|
480
|
+
// Use multi-select behavior (isSingleSelect = false)
|
|
481
|
+
handleOptionClick(el, field, false);
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
317
486
|
// =============================================================================
|
|
318
487
|
// TEXT/NUMBER INPUT RENDERING
|
|
319
488
|
// =============================================================================
|
|
@@ -333,6 +502,10 @@ function renderTextInput(field, inputType = 'text') {
|
|
|
333
502
|
return;
|
|
334
503
|
}
|
|
335
504
|
|
|
505
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
506
|
+
const existingValue = chatState.data[field];
|
|
507
|
+
console.log(`📝 Pre-filling ${field}:`, existingValue);
|
|
508
|
+
|
|
336
509
|
// Clone existing input element
|
|
337
510
|
const clone = existingInput.cloneNode(true);
|
|
338
511
|
|
|
@@ -347,7 +520,15 @@ function renderTextInput(field, inputType = 'text') {
|
|
|
347
520
|
if (inputElement) {
|
|
348
521
|
inputElement.setAttribute('data-field', field);
|
|
349
522
|
inputElement.name = field;
|
|
350
|
-
|
|
523
|
+
|
|
524
|
+
// Pre-fill value if it exists
|
|
525
|
+
if (existingValue !== undefined && existingValue !== null) {
|
|
526
|
+
inputElement.value = existingValue;
|
|
527
|
+
console.log(` ✅ Pre-filled with: ${existingValue}`);
|
|
528
|
+
} else {
|
|
529
|
+
inputElement.value = '';
|
|
530
|
+
}
|
|
531
|
+
|
|
351
532
|
inputElement.type = inputType === 'number' ? 'number' : 'text';
|
|
352
533
|
|
|
353
534
|
// Add input event to enable Next button when user types
|
|
@@ -639,6 +820,14 @@ async function showNextStep() {
|
|
|
639
820
|
} else {
|
|
640
821
|
enableNextButton();
|
|
641
822
|
}
|
|
823
|
+
} else if (inputType === 'multi-select-color') {
|
|
824
|
+
// Render color options with color blocks
|
|
825
|
+
renderColorOptions(nextStep.input.options, nextStep.input.field);
|
|
826
|
+
|
|
827
|
+
// Enable Next button if input not required (default behavior)
|
|
828
|
+
if (!inputRequired) {
|
|
829
|
+
enableNextButton();
|
|
830
|
+
}
|
|
642
831
|
} else {
|
|
643
832
|
// Render options (single-select or multi-select)
|
|
644
833
|
const isSingleSelect = inputType === 'single-select';
|
|
@@ -675,6 +864,13 @@ async function showNextStep() {
|
|
|
675
864
|
function handleCompletion() {
|
|
676
865
|
console.log('✅ Flow completed. Data:', chatState.data);
|
|
677
866
|
|
|
867
|
+
// Mark flow as completed
|
|
868
|
+
chatState.completed = true;
|
|
869
|
+
console.log(' 🏁 Flow marked as completed');
|
|
870
|
+
|
|
871
|
+
// Hide all edit icons
|
|
872
|
+
updateEditIcons();
|
|
873
|
+
|
|
678
874
|
// Hide Next button
|
|
679
875
|
if (elements.nextBtn) {
|
|
680
876
|
elements.nextBtn.style.display = 'none';
|
|
@@ -814,10 +1010,9 @@ function goToStep(stepNumber) {
|
|
|
814
1010
|
chatState.step = stepNumber;
|
|
815
1011
|
chatState.currentSelection = null;
|
|
816
1012
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
1013
|
+
// DON'T clear messages - keep chat history visible!
|
|
1014
|
+
// User can see their previous answers
|
|
1015
|
+
console.log(` Keeping messages visible (not clearing)...`);
|
|
821
1016
|
|
|
822
1017
|
console.log(` Disabling next button and calling showNextStep()...`);
|
|
823
1018
|
disableNextButton();
|
|
@@ -836,6 +1031,12 @@ function editStep(stepNumber) {
|
|
|
836
1031
|
// Save where we are now so we can return after editing
|
|
837
1032
|
chatState.returnToStep = chatState.step;
|
|
838
1033
|
console.log(` 💾 Saved return step: ${chatState.returnToStep}`);
|
|
1034
|
+
|
|
1035
|
+
// Reset completed flag since we're editing
|
|
1036
|
+
if (chatState.completed) {
|
|
1037
|
+
chatState.completed = false;
|
|
1038
|
+
console.log(` 🔓 Flow unmarked as completed (editing)`);
|
|
1039
|
+
}
|
|
839
1040
|
|
|
840
1041
|
console.log(` Clearing history from step ${stepNumber} forward...`);
|
|
841
1042
|
// Clear history from this step forward
|