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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +223 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
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
- // Only show if this step has input AND it's a PREVIOUS step (not current)
115
- if (hasInput && stepNumber !== null && stepNumber < chatState.step) {
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
- // Only show if has input AND it's a previous step
159
- if (hasInput && stepNumber !== null && stepNumber < chatState.step) {
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
- // Show icon if: has input AND is previous step
195
- if (hasInput && stepNumber < chatState.step) {
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 === chatState.step) {
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
- // Reset to unchecked state
259
- clone.classList.remove('cf-checked');
260
- clone.style.backgroundColor = 'transparent';
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 = false; // Ensure unchecked initially
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 ensure tick icon is hidden initially
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
- inputElement.value = '';
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
- console.log(` Clearing messages container...`);
818
- if (elements.messages) {
819
- elements.messages.innerHTML = '';
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