lisichatbot 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +158 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
package/src/index.js CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Conversational Flow Builder
3
+ * A simple, flexible conversational flow builder for multi-step chat interfaces
4
+ * @version 1.0.0
5
+ * WITH CUSTOM DATA ATTRIBUTES
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // =============================================================================
11
+ // STATE MANAGEMENT
12
+ // =============================================================================
1
13
 
2
14
  let chatState = {
3
15
  step: 0,
@@ -52,19 +64,43 @@ function disableNextButton() {
52
64
  // MESSAGE FUNCTIONS
53
65
  // =============================================================================
54
66
 
55
- function addMessage(content, type = 'bot') {
67
+ function addMessage(content, type = 'bot', isEditable = false, stepNumber = null) {
56
68
  if (!elements.messages) return;
57
69
 
58
70
  const messageDiv = document.createElement('div');
59
71
  messageDiv.className = `cf-message cf-${type}`;
60
- messageDiv.innerHTML = `<div class="cf-message-content">${content}</div>`;
72
+ messageDiv.setAttribute('data-chat-element', `${type}-message-wrapper`);
73
+
74
+ if (stepNumber !== null) {
75
+ messageDiv.setAttribute('data-chat-step', stepNumber);
76
+ }
77
+
78
+ let editIcon = '';
79
+ if (isEditable && type === 'user') {
80
+ editIcon = `
81
+ <button class="cf-edit-icon"
82
+ data-chat-element="edit-button"
83
+ data-chat-step="${stepNumber}"
84
+ onclick="editStep(${stepNumber})"
85
+ title="Edit this response">
86
+ ✏️
87
+ </button>
88
+ `;
89
+ }
90
+
91
+ messageDiv.innerHTML = `
92
+ <div class="cf-message-content" data-chat-element="${type}-message-text">
93
+ ${content}
94
+ </div>
95
+ ${editIcon}
96
+ `;
61
97
 
62
98
  elements.messages.appendChild(messageDiv);
63
99
  scrollToBottom();
64
100
  }
65
101
 
66
102
  // =============================================================================
67
- // OPTIONS RENDERING
103
+ // OPTIONS RENDERING WITH CUSTOM ATTRIBUTES
68
104
  // =============================================================================
69
105
 
70
106
  function renderOptions(options, field, isSingleSelect = true) {
@@ -72,6 +108,10 @@ function renderOptions(options, field, isSingleSelect = true) {
72
108
 
73
109
  const messageDiv = document.createElement('div');
74
110
  messageDiv.className = 'cf-message cf-bot';
111
+ messageDiv.setAttribute('data-chat-message', 'bot');
112
+
113
+ // Determine input type attribute value
114
+ const inputTypeAttr = isSingleSelect ? 'single-select-input' : 'multi-select-input';
75
115
 
76
116
  const optionsHtml = options.map((option, index) => {
77
117
  const optionName = option.name || option;
@@ -81,45 +121,59 @@ function renderOptions(options, field, isSingleSelect = true) {
81
121
 
82
122
  return `
83
123
  <label class="cf-option ${isSingleSelect ? 'cf-single' : 'cf-multi'}"
124
+ data-chat-input-element="container"
125
+ data-chat-element="${inputTypeAttr}"
84
126
  data-field="${field}"
85
127
  data-value='${valueStr}'
86
128
  data-name="${optionName}"
87
129
  data-index="${index}">
88
- <div class="cf-tick" style="display: none;">✓</div>
130
+
131
+ <div class="cf-tick"
132
+ data-chat-input-element="tick-icon"
133
+ style="display: none;">✓</div>
134
+
89
135
  <input type="${isSingleSelect ? 'radio' : 'checkbox'}"
90
136
  name="${field}"
91
137
  value="${valueStr}"
138
+ data-chat-input-element="input"
139
+ data-chat-element="${inputTypeAttr}"
92
140
  style="display: none;">
93
- <span class="cf-option-text">${optionName}</span>
141
+
142
+ <span class="cf-option-text"
143
+ data-chat-input-element="text">${optionName}</span>
94
144
  </label>
95
145
  `;
96
146
  }).join('');
97
147
 
98
148
  messageDiv.innerHTML = `
99
- <div class="cf-message-content">
100
- <div class="cf-options">${optionsHtml}</div>
149
+ <div class="cf-message-content" data-chat-element="bot-message-text">
150
+ <div class="cf-options" data-chat-element="options-wrapper">${optionsHtml}</div>
101
151
  </div>
102
152
  `;
103
153
 
104
154
  elements.messages.appendChild(messageDiv);
105
155
  scrollToBottom();
106
156
 
107
- // Add click handlers
108
- const optionElements = messageDiv.querySelectorAll('.cf-option');
157
+ // Add click handlers using custom attribute selector
158
+ const optionElements = messageDiv.querySelectorAll('[data-chat-input-element="container"]');
109
159
  optionElements.forEach(el => {
110
160
  el.onclick = () => handleOptionClick(el, field, isSingleSelect);
111
161
  });
112
162
  }
113
163
 
114
164
  // =============================================================================
115
- // OPTION CLICK HANDLER
165
+ // OPTION CLICK HANDLER USING CUSTOM ATTRIBUTES
116
166
  // =============================================================================
117
167
 
118
168
  function handleOptionClick(element, field, isSingleSelect) {
119
- const tickIcon = element.querySelector('.cf-tick');
120
- const input = element.querySelector('input');
169
+ // Find elements using custom attributes
170
+ const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
171
+ const input = element.querySelector('[data-chat-input-element="input"]');
172
+ const textElement = element.querySelector('[data-chat-input-element="text"]');
173
+
121
174
  const valueStr = element.getAttribute('data-value');
122
175
  const optionName = element.getAttribute('data-name');
176
+ const inputType = element.getAttribute('data-chat-element');
123
177
 
124
178
  // Parse value
125
179
  let value;
@@ -130,20 +184,26 @@ function handleOptionClick(element, field, isSingleSelect) {
130
184
  }
131
185
 
132
186
  if (isSingleSelect) {
133
- // Uncheck all others
134
- const allOptions = document.querySelectorAll(`[data-field="${field}"]`);
187
+ // Uncheck all others with same field and input type
188
+ const selector = `[data-field="${field}"][data-chat-element="${inputType}"]`;
189
+ const allOptions = document.querySelectorAll(selector);
190
+
135
191
  allOptions.forEach(opt => {
136
192
  opt.classList.remove('cf-checked');
137
193
  opt.style.backgroundColor = 'transparent';
138
- opt.querySelector('.cf-tick').style.display = 'none';
139
- opt.querySelector('input').checked = false;
194
+
195
+ const otherTick = opt.querySelector('[data-chat-input-element="tick-icon"]');
196
+ const otherInput = opt.querySelector('[data-chat-input-element="input"]');
197
+
198
+ if (otherTick) otherTick.style.display = 'none';
199
+ if (otherInput) otherInput.checked = false;
140
200
  });
141
201
 
142
202
  // Check this one
143
203
  element.classList.add('cf-checked');
144
204
  element.style.backgroundColor = config.selectedBackground;
145
- tickIcon.style.display = 'block';
146
- input.checked = true;
205
+ if (tickIcon) tickIcon.style.display = 'block';
206
+ if (input) input.checked = true;
147
207
 
148
208
  // Save value
149
209
  chatState.data[field] = value;
@@ -156,8 +216,8 @@ function handleOptionClick(element, field, isSingleSelect) {
156
216
  if (isChecked) {
157
217
  element.classList.add('cf-checked');
158
218
  element.style.backgroundColor = config.selectedBackground;
159
- tickIcon.style.display = 'block';
160
- input.checked = true;
219
+ if (tickIcon) tickIcon.style.display = 'block';
220
+ if (input) input.checked = true;
161
221
 
162
222
  // Add to array
163
223
  if (!Array.isArray(chatState.data[field])) {
@@ -173,8 +233,8 @@ function handleOptionClick(element, field, isSingleSelect) {
173
233
  } else {
174
234
  element.classList.remove('cf-checked');
175
235
  element.style.backgroundColor = 'transparent';
176
- tickIcon.style.display = 'none';
177
- input.checked = false;
236
+ if (tickIcon) tickIcon.style.display = 'none';
237
+ if (input) input.checked = false;
178
238
 
179
239
  // Remove from array
180
240
  if (Array.isArray(chatState.data[field])) {
@@ -184,8 +244,9 @@ function handleOptionClick(element, field, isSingleSelect) {
184
244
  }
185
245
  }
186
246
 
187
- // Get all selected names
188
- const selectedOptions = document.querySelectorAll(`[data-field="${field}"].cf-checked`);
247
+ // Get all selected names using custom attribute
248
+ const selector = `[data-field="${field}"][data-chat-element="${inputType}"].cf-checked`;
249
+ const selectedOptions = document.querySelectorAll(selector);
189
250
  const selectedNames = Array.from(selectedOptions).map(opt =>
190
251
  opt.getAttribute('data-name')
191
252
  ).join(', ');
@@ -231,8 +292,8 @@ async function handleNext() {
231
292
  }
232
293
  }
233
294
 
234
- // Add user message
235
- addMessage(chatState.currentSelection.name, 'user');
295
+ // Add user message with edit capability
296
+ addMessage(chatState.currentSelection.name, 'user', true, chatState.step);
236
297
 
237
298
  // Save to history
238
299
  chatState.history.push({
@@ -327,11 +388,13 @@ function handleCompletion() {
327
388
  // INITIALIZATION
328
389
  // =============================================================================
329
390
 
330
- function init(containerId, flowConfig) {
331
- // Get container
332
- elements.container = document.getElementById(containerId);
391
+ function init(flowName, flowConfig) {
392
+ // Find container by data-chat-element="chat-wrapper"
393
+ elements.container = document.querySelector('[data-chat-element="chat-wrapper"]');
394
+
333
395
  if (!elements.container) {
334
- console.error('Container not found:', containerId);
396
+ console.error('Container with data-chat-element="chat-wrapper" not found');
397
+ console.error('Please add <div data-chat-element="chat-wrapper"></div> to your HTML');
335
398
  return null;
336
399
  }
337
400
 
@@ -349,36 +412,42 @@ function init(containerId, flowConfig) {
349
412
  chatState.history = [];
350
413
  chatState.currentSelection = null;
351
414
 
352
- // Create UI
415
+ // Clear existing content and create structure
353
416
  elements.container.innerHTML = `
354
- <div class="cf-wrapper">
355
- <div class="cf-messages" id="cfMessages"></div>
356
- <div class="cf-bottom">
357
- <button id="cfNextBtn" class="cf-next-btn" style="opacity: 0.5; cursor: not-allowed;" disabled>
358
- Next →
359
- </button>
360
- </div>
417
+ <div data-chat-element="messages-container"></div>
418
+ <div data-chat-element="navigation-bottom">
419
+ <button data-chat-element="next-button"
420
+ style="opacity: 0.5; cursor: not-allowed;"
421
+ disabled>
422
+ Next →
423
+ </button>
361
424
  </div>
362
425
  `;
363
426
 
364
- // Get elements
365
- elements.messages = document.getElementById('cfMessages');
366
- elements.nextBtn = document.getElementById('cfNextBtn');
427
+ // Get elements by data-chat-element
428
+ elements.messages = elements.container.querySelector('[data-chat-element="messages-container"]');
429
+ elements.nextBtn = elements.container.querySelector('[data-chat-element="next-button"]');
367
430
 
368
431
  // Add event listener
369
432
  if (elements.nextBtn) {
370
433
  elements.nextBtn.onclick = handleNext;
371
434
  }
372
435
 
436
+ // Expose editStep globally for onclick handlers
437
+ if (typeof window !== 'undefined') {
438
+ window.editStep = editStep;
439
+ }
440
+
373
441
  // Start
374
442
  showNextStep();
375
443
 
376
- console.log('✅ Conversational Flow Builder initialized');
444
+ console.log(`✅ Flow "${flowName}" initialized`);
377
445
 
378
446
  return {
379
447
  getState,
380
448
  reset,
381
- goToStep
449
+ goToStep,
450
+ editStep
382
451
  };
383
452
  }
384
453
 
@@ -425,6 +494,27 @@ function goToStep(stepNumber) {
425
494
  showNextStep();
426
495
  }
427
496
 
497
+ function editStep(stepNumber) {
498
+ if (stepNumber < 0 || stepNumber >= flowData.flow.length) {
499
+ console.error('Invalid step number:', stepNumber);
500
+ return;
501
+ }
502
+
503
+ // Clear history from this step forward
504
+ chatState.history = chatState.history.filter(h => h.step < stepNumber);
505
+
506
+ // Reset data for this field and beyond
507
+ const stepsToReset = flowData.flow.slice(stepNumber);
508
+ stepsToReset.forEach(step => {
509
+ if (step.input && step.input.field) {
510
+ delete chatState.data[step.input.field];
511
+ }
512
+ });
513
+
514
+ // Go to that step
515
+ goToStep(stepNumber);
516
+ }
517
+
428
518
  function injectStyles() {
429
519
  if (typeof document === 'undefined') return;
430
520
 
@@ -454,6 +544,7 @@ function injectStyles() {
454
544
  .cf-message {
455
545
  margin-bottom: 15px;
456
546
  animation: cfSlideIn 0.3s ease;
547
+ position: relative;
457
548
  }
458
549
 
459
550
  @keyframes cfSlideIn {
@@ -469,6 +560,28 @@ function injectStyles() {
469
560
  text-align: right;
470
561
  }
471
562
 
563
+ .cf-edit-icon {
564
+ position: absolute;
565
+ right: -30px;
566
+ top: 50%;
567
+ transform: translateY(-50%);
568
+ background: none;
569
+ border: none;
570
+ cursor: pointer;
571
+ font-size: 16px;
572
+ opacity: 0;
573
+ transition: opacity 0.2s ease;
574
+ padding: 4px 8px;
575
+ }
576
+
577
+ .cf-message:hover .cf-edit-icon {
578
+ opacity: 1;
579
+ }
580
+
581
+ .cf-edit-icon:hover {
582
+ transform: translateY(-50%) scale(1.2);
583
+ }
584
+
472
585
  .cf-message-content {
473
586
  display: inline-block;
474
587
  padding: 12px 16px;
@@ -579,5 +692,6 @@ module.exports = {
579
692
  init,
580
693
  getState,
581
694
  reset,
582
- goToStep
695
+ goToStep,
696
+ editStep
583
697
  };