handhold-sdk 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 +427 -0
- package/dist/d44f1fe8001e32f8e74d.svg +3056 -0
- package/dist/fonts/Phosphor.ttf +0 -0
- package/dist/fonts/Phosphor.woff +0 -0
- package/dist/fonts/Phosphor.woff2 +0 -0
- package/dist/handhold.min.js +1 -0
- package/package.json +114 -0
- package/src/HandHold.js +1599 -0
- package/src/agent/VENDOR.md +203 -0
- package/src/agent/agent/AgentLoop.js +366 -0
- package/src/agent/agent/prompts.js +163 -0
- package/src/agent/agent/tools.js +148 -0
- package/src/agent/controller.js +235 -0
- package/src/agent/dom/actions.js +456 -0
- package/src/agent/dom/domTree.js +1761 -0
- package/src/agent/dom/redact.js +98 -0
- package/src/agent/dom/serializer.js +332 -0
- package/src/agent/dom/utils.js +99 -0
- package/src/agent/index.js +169 -0
- package/src/agent/llm/connector.js +143 -0
- package/src/agent/package.json +3 -0
- package/src/agent/policy/PolicyGuard.js +172 -0
- package/src/agent/site/manifest.js +145 -0
- package/src/agent/ui/ChatWidget.js +219 -0
- package/src/agent-mode/AgentMode.js +429 -0
- package/src/api/APIClient.js +258 -0
- package/src/core/Config.js +207 -0
- package/src/core/UiState.js +83 -0
- package/src/core/defaults.js +20 -0
- package/src/events.js +59 -0
- package/src/index.js +77 -0
- package/src/intent/ActionVerbMap.js +324 -0
- package/src/intent/IntentExtractor.js +317 -0
- package/src/observers/ElementScanner.js +284 -0
- package/src/observers/NavigationObserver.js +182 -0
- package/src/observers/PageObserver.js +293 -0
- package/src/session/SessionManager.js +402 -0
- package/src/session/SessionStorage.js +148 -0
- package/src/ui/HelpWidget.js +1189 -0
- package/src/ui/UICoach.js +1725 -0
- package/src/ui/components/Button.css +137 -0
- package/src/ui/components/Button.js +102 -0
- package/src/ui/widget.css +599 -0
- package/src/utils/Logger.js +132 -0
- package/src/utils/MarkdownRenderer.js +39 -0
- package/src/utils/domUtils.js +350 -0
- package/src/utils/eventBus.js +102 -0
- package/src/utils/index.js +8 -0
- package/src/utils/stringUtils.js +202 -0
- package/types/index.d.ts +538 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand Hold SDK - Configuration Management
|
|
3
|
+
* Handles SDK configuration with defaults and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_BASE_URL } from './defaults.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
// Required
|
|
10
|
+
apiKey: null,
|
|
11
|
+
baseUrl: null,
|
|
12
|
+
|
|
13
|
+
// UI Customization
|
|
14
|
+
theme: 'light', // 'light' | 'dark'
|
|
15
|
+
position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
16
|
+
primaryColor: '#004AF5', // Primary accent color (PaidHR Blue 500)
|
|
17
|
+
zIndex: 999990, // Base z-index for SDK elements
|
|
18
|
+
|
|
19
|
+
// Behavior
|
|
20
|
+
minHighlightConfidence: 0.70, // Minimum confidence to highlight element
|
|
21
|
+
sessionInactivityTimeout: 600000, // Session timeout (10 minutes)
|
|
22
|
+
autoOpenOnLoad: false, // Auto-open help widget on page load
|
|
23
|
+
deferRestore: false, // If true, init() skips session restore; the host calls
|
|
24
|
+
// HandHold.restoreSession() after patching its own renderer
|
|
25
|
+
// (Phase 1.4 — avoids the legacy widget drawing the confirm)
|
|
26
|
+
enableMultiPageTracking: true, // Track guidance across pages
|
|
27
|
+
showHelpButton: true, // Show floating help button
|
|
28
|
+
showStepHighlight: true, // Draw the SDK's own overlay/arrow/tooltip on the
|
|
29
|
+
// current step's element. Turn OFF when the host
|
|
30
|
+
// renders its own on-page indicator (avoids a
|
|
31
|
+
// duplicate highlight); completion is still detected
|
|
32
|
+
// — the SDK watches the real element either way.
|
|
33
|
+
agentMode: false, // "Do it for me" agent (H5) — off unless the tenant enables it
|
|
34
|
+
enableScreenshots: true, // Capture a page screenshot (html2canvas) for vision intent
|
|
35
|
+
// extraction. Turn OFF on sites using modern CSS colors
|
|
36
|
+
// (oklch/oklab, e.g. Tailwind v4): html2canvas can't parse
|
|
37
|
+
// them and stalls for seconds before failing. The agent
|
|
38
|
+
// never needs screenshots (it uses the DOM engine).
|
|
39
|
+
|
|
40
|
+
// API
|
|
41
|
+
apiTimeout: 30000, // API request timeout (30 seconds)
|
|
42
|
+
retryAttempts: 3, // Number of retry attempts
|
|
43
|
+
retryDelay: 1000, // Delay between retries
|
|
44
|
+
|
|
45
|
+
// Debug
|
|
46
|
+
debug: false, // Enable debug mode
|
|
47
|
+
logLevel: 'warn', // 'error' | 'warn' | 'info' | 'debug'
|
|
48
|
+
|
|
49
|
+
// Help Button
|
|
50
|
+
helpButtonText: '?', // Text/icon for help button
|
|
51
|
+
helpButtonAriaLabel: 'Open help', // Accessibility label
|
|
52
|
+
|
|
53
|
+
// Widget
|
|
54
|
+
widgetTitle: 'How can I help?', // Widget title
|
|
55
|
+
widgetPlaceholder: 'Type your question...', // Search input placeholder
|
|
56
|
+
maxIntents: 6, // Maximum intents to show
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
class Config {
|
|
60
|
+
constructor(userConfig = {}) {
|
|
61
|
+
// Validate required fields
|
|
62
|
+
if (!userConfig.apiKey) {
|
|
63
|
+
throw new Error('[HandHold Config] apiKey is required');
|
|
64
|
+
}
|
|
65
|
+
// baseUrl may come from the call, or the build-time baked default
|
|
66
|
+
// (HANDHOLD_DEFAULT_BASE_URL). Only an unresolvable baseUrl is an error.
|
|
67
|
+
const resolvedBaseUrl = userConfig.baseUrl || DEFAULT_BASE_URL;
|
|
68
|
+
if (!resolvedBaseUrl) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'[HandHold Config] baseUrl is required — pass baseUrl, set data-handhold-base-url, ' +
|
|
71
|
+
'or build the SDK with HANDHOLD_DEFAULT_BASE_URL'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Merge with defaults
|
|
76
|
+
this._config = { ...DEFAULT_CONFIG, ...userConfig, baseUrl: resolvedBaseUrl };
|
|
77
|
+
|
|
78
|
+
// Normalize baseUrl (remove trailing slash)
|
|
79
|
+
this._config.baseUrl = this._config.baseUrl.replace(/\/$/, '');
|
|
80
|
+
|
|
81
|
+
// Validate values
|
|
82
|
+
this._validate();
|
|
83
|
+
|
|
84
|
+
// Freeze config to prevent modification
|
|
85
|
+
Object.freeze(this._config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate configuration values
|
|
90
|
+
*/
|
|
91
|
+
_validate() {
|
|
92
|
+
const { theme, position, logLevel, minHighlightConfidence } = this._config;
|
|
93
|
+
|
|
94
|
+
// Validate theme
|
|
95
|
+
if (!['light', 'dark'].includes(theme)) {
|
|
96
|
+
console.warn(`[HandHold Config] Invalid theme "${theme}", using "light"`);
|
|
97
|
+
this._config.theme = 'light';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Validate position
|
|
101
|
+
const validPositions = ['bottom-right', 'bottom-left', 'top-right', 'top-left'];
|
|
102
|
+
if (!validPositions.includes(position)) {
|
|
103
|
+
console.warn(`[HandHold Config] Invalid position "${position}", using "bottom-right"`);
|
|
104
|
+
this._config.position = 'bottom-right';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate log level
|
|
108
|
+
const validLogLevels = ['error', 'warn', 'info', 'debug'];
|
|
109
|
+
if (!validLogLevels.includes(logLevel)) {
|
|
110
|
+
console.warn(`[HandHold Config] Invalid logLevel "${logLevel}", using "warn"`);
|
|
111
|
+
this._config.logLevel = 'warn';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate confidence threshold
|
|
115
|
+
if (minHighlightConfidence < 0 || minHighlightConfidence > 1) {
|
|
116
|
+
console.warn(`[HandHold Config] minHighlightConfidence must be between 0 and 1, using 0.85`);
|
|
117
|
+
this._config.minHighlightConfidence = 0.85;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get configuration value
|
|
123
|
+
* @param {string} key - Config key
|
|
124
|
+
* @returns {*} - Config value
|
|
125
|
+
*/
|
|
126
|
+
get(key) {
|
|
127
|
+
return this._config[key];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get all configuration as object
|
|
132
|
+
* @returns {Object}
|
|
133
|
+
*/
|
|
134
|
+
getAll() {
|
|
135
|
+
return { ...this._config };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if debug mode is enabled
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
get isDebug() {
|
|
143
|
+
return this._config.debug;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Convenience getters
|
|
147
|
+
get apiKey() { return this._config.apiKey; }
|
|
148
|
+
get baseUrl() { return this._config.baseUrl; }
|
|
149
|
+
get theme() { return this._config.theme; }
|
|
150
|
+
get position() { return this._config.position; }
|
|
151
|
+
get primaryColor() { return this._config.primaryColor; }
|
|
152
|
+
get zIndex() { return this._config.zIndex; }
|
|
153
|
+
get agentMode() { return this._config.agentMode; }
|
|
154
|
+
get minHighlightConfidence() { return this._config.minHighlightConfidence; }
|
|
155
|
+
get sessionInactivityTimeout() { return this._config.sessionInactivityTimeout; }
|
|
156
|
+
get enableMultiPageTracking() { return this._config.enableMultiPageTracking; }
|
|
157
|
+
get logLevel() { return this._config.logLevel; }
|
|
158
|
+
get maxIntents() { return this._config.maxIntents; }
|
|
159
|
+
get showHelpButton() { return this._config.showHelpButton; }
|
|
160
|
+
get apiTimeout() { return this._config.apiTimeout; }
|
|
161
|
+
get retryAttempts() { return this._config.retryAttempts; }
|
|
162
|
+
get retryDelay() { return this._config.retryDelay; }
|
|
163
|
+
get widgetTitle() { return this._config.widgetTitle; }
|
|
164
|
+
get widgetPlaceholder() { return this._config.widgetPlaceholder; }
|
|
165
|
+
get helpButtonText() { return this._config.helpButtonText; }
|
|
166
|
+
get helpButtonAriaLabel() { return this._config.helpButtonAriaLabel; }
|
|
167
|
+
get autoOpenOnLoad() { return this._config.autoOpenOnLoad; }
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get CSS variables for theming
|
|
171
|
+
* @returns {Object}
|
|
172
|
+
*/
|
|
173
|
+
getCSSVariables() {
|
|
174
|
+
const isDark = this._config.theme === 'dark';
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
'--hh-primary': this._config.primaryColor,
|
|
178
|
+
'--hh-primary-light': this._adjustColor(this._config.primaryColor, 40),
|
|
179
|
+
'--hh-primary-dark': this._adjustColor(this._config.primaryColor, -40),
|
|
180
|
+
'--hh-bg': isDark ? '#1e1e1e' : '#ffffff',
|
|
181
|
+
'--hh-bg-secondary': isDark ? '#2d2d2d' : '#f5f5f5',
|
|
182
|
+
'--hh-text': isDark ? '#ffffff' : '#333333',
|
|
183
|
+
'--hh-text-secondary': isDark ? '#b0b0b0' : '#666666',
|
|
184
|
+
'--hh-border': isDark ? '#404040' : '#e0e0e0',
|
|
185
|
+
'--hh-shadow': isDark ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.15)',
|
|
186
|
+
'--hh-overlay': 'rgba(0, 0, 0, 0.5)',
|
|
187
|
+
'--hh-z-index': this._config.zIndex,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Adjust color brightness
|
|
193
|
+
* @param {string} color - Hex color
|
|
194
|
+
* @param {number} amount - Amount to adjust (-255 to 255)
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
_adjustColor(color, amount) {
|
|
198
|
+
const hex = color.replace('#', '');
|
|
199
|
+
const r = Math.max(0, Math.min(255, parseInt(hex.substr(0, 2), 16) + amount));
|
|
200
|
+
const g = Math.max(0, Math.min(255, parseInt(hex.substr(2, 2), 16) + amount));
|
|
201
|
+
const b = Math.max(0, Math.min(255, parseInt(hex.substr(4, 2), 16) + amount));
|
|
202
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default Config;
|
|
207
|
+
export { DEFAULT_CONFIG };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand Hold SDK - UiState (Phase 2.1)
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for the widget UI. A tiny observable store shaped
|
|
5
|
+
* for React's useSyncExternalStore: getSnapshot() returns a STABLE object
|
|
6
|
+
* reference until setState() replaces it, and subscribe(fn) is notified on every
|
|
7
|
+
* change. The legacy DOM widget and any host renderer (the React demo) both read
|
|
8
|
+
* from this one store, so the two can no longer drift.
|
|
9
|
+
*
|
|
10
|
+
* State shape (all serializable):
|
|
11
|
+
* view 'menu' | 'loading' | 'guidance' | 'article' | 'error'
|
|
12
|
+
* | 'clarification' | 'completion'
|
|
13
|
+
* isOpen whether the widget panel is open
|
|
14
|
+
* intents scanned intent list for the menu
|
|
15
|
+
* guidance active guidance object (steps, current_step, agent_plan, …)
|
|
16
|
+
* article KB article being shown
|
|
17
|
+
* error error message string
|
|
18
|
+
* clarification { question, options } for an ambiguous query
|
|
19
|
+
* completion completion label
|
|
20
|
+
* loadingLabel label for the loading view
|
|
21
|
+
* confirm { message } for the session-restore confirm
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const DEFAULT_STATE = {
|
|
25
|
+
view: 'menu',
|
|
26
|
+
isOpen: false,
|
|
27
|
+
intents: [],
|
|
28
|
+
guidance: null,
|
|
29
|
+
article: null,
|
|
30
|
+
error: null,
|
|
31
|
+
clarification: null,
|
|
32
|
+
completion: null,
|
|
33
|
+
loadingLabel: null,
|
|
34
|
+
confirm: null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default class UiState {
|
|
38
|
+
constructor(initial = {}) {
|
|
39
|
+
this._state = { ...DEFAULT_STATE, ...initial };
|
|
40
|
+
this._listeners = new Set();
|
|
41
|
+
// Bound so it can be passed directly as useSyncExternalStore(subscribe, …).
|
|
42
|
+
this.subscribe = this.subscribe.bind(this);
|
|
43
|
+
this.getSnapshot = this.getSnapshot.bind(this);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Current immutable snapshot (stable reference until setState). */
|
|
47
|
+
getSnapshot() {
|
|
48
|
+
return this._state;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to state changes. The listener is called with the new snapshot.
|
|
53
|
+
* @returns {() => void} unsubscribe
|
|
54
|
+
*/
|
|
55
|
+
subscribe(listener) {
|
|
56
|
+
this._listeners.add(listener);
|
|
57
|
+
return () => this._listeners.delete(listener);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Merge a partial update, producing a new snapshot, and notify listeners. */
|
|
61
|
+
setState(partial) {
|
|
62
|
+
this._state = { ...this._state, ...partial };
|
|
63
|
+
this._notify();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Reset to defaults (e.g. on close/destroy). */
|
|
67
|
+
reset() {
|
|
68
|
+
this._state = { ...DEFAULT_STATE };
|
|
69
|
+
this._notify();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_notify() {
|
|
73
|
+
for (const listener of this._listeners) {
|
|
74
|
+
// One bad subscriber must not break the others (or the state update).
|
|
75
|
+
try {
|
|
76
|
+
listener(this._state);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.error('[HandHold UiState] subscriber threw:', e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand Hold SDK - Build-time defaults
|
|
3
|
+
*
|
|
4
|
+
* The default backend URL is settable by whoever builds the bundle. A Model B
|
|
5
|
+
* self-hoster runs:
|
|
6
|
+
*
|
|
7
|
+
* HANDHOLD_DEFAULT_BASE_URL=https://help.acme.com npm run build
|
|
8
|
+
*
|
|
9
|
+
* and the served `handhold.js` already points at THEIR backend — so their pages
|
|
10
|
+
* only need `data-handhold-api-key`, not `data-handhold-base-url` on every embed.
|
|
11
|
+
*
|
|
12
|
+
* `__HANDHOLD_DEFAULT_BASE_URL__` is replaced at build time by webpack's
|
|
13
|
+
* DefinePlugin. The `typeof` guard keeps this safe under jest/node (where the
|
|
14
|
+
* global is not defined) — there it resolves to '' and callers fall back to an
|
|
15
|
+
* explicit value or same-origin.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_BASE_URL =
|
|
18
|
+
(typeof __HANDHOLD_DEFAULT_BASE_URL__ !== 'undefined' && __HANDHOLD_DEFAULT_BASE_URL__) || '';
|
|
19
|
+
|
|
20
|
+
export default DEFAULT_BASE_URL;
|
package/src/events.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand Hold SDK - Event registry (Phase 6.3)
|
|
3
|
+
*
|
|
4
|
+
* One place that names every EventBus event. Stringly-typed event names caused
|
|
5
|
+
* real bugs (an unhandled 'ui:highlight_request', a missing 'session:close'
|
|
6
|
+
* wiring): a typo in an emit or on had no compile-time signal. Reference these
|
|
7
|
+
* constants instead of raw strings so emit and on always agree.
|
|
8
|
+
*
|
|
9
|
+
* Values are IDENTICAL to the historical string literals, so this can be
|
|
10
|
+
* adopted incrementally — code still using a raw string interops with code
|
|
11
|
+
* using the constant.
|
|
12
|
+
*/
|
|
13
|
+
export const EVENTS = Object.freeze({
|
|
14
|
+
// Lifecycle
|
|
15
|
+
READY: 'handhold:ready',
|
|
16
|
+
CLOSED: 'handhold:closed',
|
|
17
|
+
STATE_CHANGED: 'state:changed',
|
|
18
|
+
|
|
19
|
+
// User/host commands (host -> SDK)
|
|
20
|
+
HELP_OPEN: 'help:open',
|
|
21
|
+
QUERY_SUBMITTED: 'query:submitted',
|
|
22
|
+
INTENT_SELECTED: 'intent:selected',
|
|
23
|
+
STEP_DONE: 'step:done',
|
|
24
|
+
STEP_SKIP: 'step:skip',
|
|
25
|
+
STEP_BACK: 'step:back',
|
|
26
|
+
SESSION_CLOSE: 'session:close',
|
|
27
|
+
HELP_MINIMIZE: 'help:minimize',
|
|
28
|
+
HELP_SHOW_HINT: 'help:show_hint',
|
|
29
|
+
CONFIRM_RESPONSE: 'hh:confirm:response',
|
|
30
|
+
|
|
31
|
+
// Page / navigation
|
|
32
|
+
NAVIGATION_CHANGED: 'navigation:changed',
|
|
33
|
+
PAGE_DOM_CHANGED: 'page:dom_changed',
|
|
34
|
+
STEP_SHOW_UI: 'step:show_ui',
|
|
35
|
+
SELECTION_EXPLAIN: 'selection:explain',
|
|
36
|
+
UI_HIGHLIGHT_REQUEST: 'ui:highlight_request',
|
|
37
|
+
|
|
38
|
+
// Highlighting (UICoach)
|
|
39
|
+
HIGHLIGHT_SHOWN: 'highlight:shown',
|
|
40
|
+
HIGHLIGHT_NOT_FOUND: 'highlight:not_found',
|
|
41
|
+
|
|
42
|
+
// Session lifecycle (SessionManager)
|
|
43
|
+
SESSION_STARTED: 'session:started',
|
|
44
|
+
SESSION_UPDATED: 'session:updated',
|
|
45
|
+
SESSION_NAVIGATION: 'session:navigation',
|
|
46
|
+
SESSION_COMPLETED: 'session:completed',
|
|
47
|
+
SESSION_STEP_ADVANCED: 'session:step_advanced',
|
|
48
|
+
SESSION_ENDED: 'session:ended',
|
|
49
|
+
|
|
50
|
+
// Agent mode
|
|
51
|
+
AGENT_START: 'agent:start',
|
|
52
|
+
AGENT_STOP: 'agent:stop',
|
|
53
|
+
AGENT_ACTIVITY: 'agent:activity',
|
|
54
|
+
AGENT_CONFIRM: 'agent:confirm',
|
|
55
|
+
AGENT_ASK: 'agent:ask',
|
|
56
|
+
AGENT_DONE: 'agent:done',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default EVENTS;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand Hold SDK - Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Context-aware, in-app help system that provides:
|
|
5
|
+
* - Intent-based help menus
|
|
6
|
+
* - Visual UI guidance with element highlighting
|
|
7
|
+
* - Multi-page workflow tracking
|
|
8
|
+
* - Conversational fallback for free-text queries
|
|
9
|
+
*
|
|
10
|
+
* @version 1.0.0
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import HandHold, { HandHold as HandHoldClass } from './HandHold.js';
|
|
15
|
+
import { DEFAULT_BASE_URL } from './core/defaults.js';
|
|
16
|
+
|
|
17
|
+
// Export core modules for advanced usage
|
|
18
|
+
export { default as Config } from './core/Config.js';
|
|
19
|
+
export { default as Logger } from './utils/Logger.js';
|
|
20
|
+
export { default as EventBus } from './utils/eventBus.js';
|
|
21
|
+
export { default as APIClient } from './api/APIClient.js';
|
|
22
|
+
export { default as PageObserver } from './observers/PageObserver.js';
|
|
23
|
+
export { default as NavigationObserver } from './observers/NavigationObserver.js';
|
|
24
|
+
export { default as ElementScanner } from './observers/ElementScanner.js';
|
|
25
|
+
export { default as IntentExtractor } from './intent/IntentExtractor.js';
|
|
26
|
+
export { default as ActionVerbMap } from './intent/ActionVerbMap.js';
|
|
27
|
+
export { default as SessionManager } from './session/SessionManager.js';
|
|
28
|
+
export { default as SessionStorage } from './session/SessionStorage.js';
|
|
29
|
+
export { default as HelpWidget } from './ui/HelpWidget.js';
|
|
30
|
+
export { default as UICoach } from './ui/UICoach.js';
|
|
31
|
+
|
|
32
|
+
// Export utilities
|
|
33
|
+
export * as domUtils from './utils/domUtils.js';
|
|
34
|
+
export * as stringUtils from './utils/stringUtils.js';
|
|
35
|
+
|
|
36
|
+
// Event registry + UI-state store (Phase 2 / 6.3)
|
|
37
|
+
export { default as EVENTS } from './events.js';
|
|
38
|
+
export { default as UiState } from './core/UiState.js';
|
|
39
|
+
|
|
40
|
+
// Export HandHold class for custom instantiation
|
|
41
|
+
export { HandHoldClass };
|
|
42
|
+
|
|
43
|
+
// Default export is the singleton instance
|
|
44
|
+
export default HandHold;
|
|
45
|
+
|
|
46
|
+
// Attach to window for non-module usage
|
|
47
|
+
if (typeof window !== 'undefined') {
|
|
48
|
+
window.HandHold = HandHold;
|
|
49
|
+
|
|
50
|
+
// Auto-initialize if data attributes are present
|
|
51
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
52
|
+
const script = document.querySelector('script[data-handhold-api-key]');
|
|
53
|
+
|
|
54
|
+
if (script) {
|
|
55
|
+
const apiKey = script.getAttribute('data-handhold-api-key');
|
|
56
|
+
// Backend URL precedence: explicit attribute > build-time baked default
|
|
57
|
+
// (HANDHOLD_DEFAULT_BASE_URL) > same-origin (works when the API is reverse
|
|
58
|
+
// -proxied under the app's domain). No dead external default.
|
|
59
|
+
const baseUrl = script.getAttribute('data-handhold-base-url')
|
|
60
|
+
|| DEFAULT_BASE_URL
|
|
61
|
+
|| (typeof window !== 'undefined' ? window.location.origin : '');
|
|
62
|
+
const theme = script.getAttribute('data-handhold-theme') || 'light';
|
|
63
|
+
const position = script.getAttribute('data-handhold-position') || 'bottom-right';
|
|
64
|
+
const debug = script.getAttribute('data-handhold-debug') === 'true';
|
|
65
|
+
|
|
66
|
+
HandHold.init({
|
|
67
|
+
apiKey,
|
|
68
|
+
baseUrl,
|
|
69
|
+
theme,
|
|
70
|
+
position,
|
|
71
|
+
debug
|
|
72
|
+
}).catch(error => {
|
|
73
|
+
console.error('[HandHold] Auto-initialization failed:', error);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|