lisichatbot 1.0.0 → 1.0.1

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 +151 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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({
@@ -349,12 +410,19 @@ function init(containerId, flowConfig) {
349
410
  chatState.history = [];
350
411
  chatState.currentSelection = null;
351
412
 
352
- // Create UI
413
+ // Create UI with data-chat-element attributes
353
414
  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>
415
+ <div class="cf-wrapper" data-chat-element="chat-wrapper">
416
+ <div class="cf-messages"
417
+ id="cfMessages"
418
+ data-chat-element="messages-container"></div>
419
+ <div class="cf-bottom"
420
+ data-chat-element="navigation-bottom">
421
+ <button id="cfNextBtn"
422
+ class="cf-next-btn"
423
+ data-chat-element="next-button"
424
+ style="opacity: 0.5; cursor: not-allowed;"
425
+ disabled>
358
426
  Next →
359
427
  </button>
360
428
  </div>
@@ -370,10 +438,15 @@ function init(containerId, flowConfig) {
370
438
  elements.nextBtn.onclick = handleNext;
371
439
  }
372
440
 
441
+ // Expose editStep globally for onclick handlers
442
+ if (typeof window !== 'undefined') {
443
+ window.editStep = editStep;
444
+ }
445
+
373
446
  // Start
374
447
  showNextStep();
375
448
 
376
- console.log('✅ Conversational Flow Builder initialized');
449
+ console.log('✅ Conversational Flow Builder initialized with custom attributes');
377
450
 
378
451
  return {
379
452
  getState,
@@ -425,6 +498,27 @@ function goToStep(stepNumber) {
425
498
  showNextStep();
426
499
  }
427
500
 
501
+ function editStep(stepNumber) {
502
+ if (stepNumber < 0 || stepNumber >= flowData.flow.length) {
503
+ console.error('Invalid step number:', stepNumber);
504
+ return;
505
+ }
506
+
507
+ // Clear history from this step forward
508
+ chatState.history = chatState.history.filter(h => h.step < stepNumber);
509
+
510
+ // Reset data for this field and beyond
511
+ const stepsToReset = flowData.flow.slice(stepNumber);
512
+ stepsToReset.forEach(step => {
513
+ if (step.input && step.input.field) {
514
+ delete chatState.data[step.input.field];
515
+ }
516
+ });
517
+
518
+ // Go to that step
519
+ goToStep(stepNumber);
520
+ }
521
+
428
522
  function injectStyles() {
429
523
  if (typeof document === 'undefined') return;
430
524
 
@@ -454,6 +548,7 @@ function injectStyles() {
454
548
  .cf-message {
455
549
  margin-bottom: 15px;
456
550
  animation: cfSlideIn 0.3s ease;
551
+ position: relative;
457
552
  }
458
553
 
459
554
  @keyframes cfSlideIn {
@@ -469,6 +564,28 @@ function injectStyles() {
469
564
  text-align: right;
470
565
  }
471
566
 
567
+ .cf-edit-icon {
568
+ position: absolute;
569
+ right: -30px;
570
+ top: 50%;
571
+ transform: translateY(-50%);
572
+ background: none;
573
+ border: none;
574
+ cursor: pointer;
575
+ font-size: 16px;
576
+ opacity: 0;
577
+ transition: opacity 0.2s ease;
578
+ padding: 4px 8px;
579
+ }
580
+
581
+ .cf-message:hover .cf-edit-icon {
582
+ opacity: 1;
583
+ }
584
+
585
+ .cf-edit-icon:hover {
586
+ transform: translateY(-50%) scale(1.2);
587
+ }
588
+
472
589
  .cf-message-content {
473
590
  display: inline-block;
474
591
  padding: 12px 16px;
@@ -579,5 +696,6 @@ module.exports = {
579
696
  init,
580
697
  getState,
581
698
  reset,
582
- goToStep
699
+ goToStep,
700
+ editStep
583
701
  };