@kelet-ai/feedback-ui 0.7.1 → 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.
- package/README.md +6 -6
- package/dist/feedback-ui.es.js +57 -40
- package/dist/feedback-ui.es.js.map +1 -1
- package/dist/feedback-ui.es.min.js +558 -557
- package/dist/feedback-ui.es.min.js.map +1 -1
- package/dist/feedback-ui.umd.js +57 -40
- package/dist/feedback-ui.umd.js.map +1 -1
- package/dist/feedback-ui.umd.min.js +9 -9
- package/dist/feedback-ui.umd.min.js.map +1 -1
- package/dist/hooks/feedback-state/types.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/lib/otel-trace.d.ts +9 -10
- package/package.json +19 -16
package/README.md
CHANGED
|
@@ -261,7 +261,7 @@ For implicit feedback, understanding how state changes are processed:
|
|
|
261
261
|
// User types: "Hello" → "Hello World" → "Hello World!"
|
|
262
262
|
// Only sends ONE feedback after user stops typing
|
|
263
263
|
const [text, setText] = useFeedbackState('', 'editor', {
|
|
264
|
-
debounceMs:
|
|
264
|
+
debounceMs: 3000, // Wait 3s after last change
|
|
265
265
|
});
|
|
266
266
|
```
|
|
267
267
|
|
|
@@ -321,18 +321,18 @@ const [data, setData] = useFeedbackState(initial, 'tracker', {
|
|
|
321
321
|
|
|
322
322
|
## 🔗 OpenTelemetry Integration
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
Prefer W3C traceparent to correlate feedback with distributed traces:
|
|
325
325
|
|
|
326
326
|
```tsx
|
|
327
|
-
import { VoteFeedback,
|
|
327
|
+
import { VoteFeedback, getTraceParent } from '@kelet-ai/feedback-ui';
|
|
328
328
|
|
|
329
|
-
<VoteFeedback.Root tx_id={
|
|
329
|
+
<VoteFeedback.Root tx_id={getTraceParent} onFeedback={handleFeedback}>
|
|
330
330
|
<VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
|
|
331
331
|
<VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
|
|
332
332
|
</VoteFeedback.Root>;
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
-
Requires `@opentelemetry/api`
|
|
335
|
+
Requires `@opentelemetry/api` for active context. If missing at runtime, an error is thrown.
|
|
336
336
|
|
|
337
337
|
---
|
|
338
338
|
|
|
@@ -535,7 +535,7 @@ interface FeedbackData {
|
|
|
535
535
|
|
|
536
536
|
| Option | Type | Default | Description |
|
|
537
537
|
| ---------------------- | ------------------------------------ | --------------------- | -------------------------------------- |
|
|
538
|
-
| `debounceMs` | `number` | `
|
|
538
|
+
| `debounceMs` | `number` | `3000` | Debounce time in milliseconds |
|
|
539
539
|
| `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Diff output format |
|
|
540
540
|
| `compareWith` | `(a: T, b: T) => boolean` | `undefined` | Custom equality function |
|
|
541
541
|
| `metadata` | `Record<string, any>` | `{}` | Additional metadata |
|
package/dist/feedback-ui.es.js
CHANGED
|
@@ -1865,7 +1865,7 @@ function splitLines(text) {
|
|
|
1865
1865
|
}
|
|
1866
1866
|
return result;
|
|
1867
1867
|
}
|
|
1868
|
-
function formatDiff(oldValue, newValue, diffType = "git", context =
|
|
1868
|
+
function formatDiff(oldValue, newValue, diffType = "git", context = 1) {
|
|
1869
1869
|
switch (diffType) {
|
|
1870
1870
|
case "git":
|
|
1871
1871
|
return formatGitDiff(oldValue, newValue, context);
|
|
@@ -1877,7 +1877,7 @@ function formatDiff(oldValue, newValue, diffType = "git", context = 3) {
|
|
|
1877
1877
|
return formatGitDiff(oldValue, newValue, context);
|
|
1878
1878
|
}
|
|
1879
1879
|
}
|
|
1880
|
-
function formatGitDiff(oldValue, newValue, context =
|
|
1880
|
+
function formatGitDiff(oldValue, newValue, context = 1) {
|
|
1881
1881
|
const oldStr = stringify(oldValue);
|
|
1882
1882
|
const newStr = stringify(newValue);
|
|
1883
1883
|
const patch = createTwoFilesPatch(
|
|
@@ -1893,7 +1893,20 @@ function formatGitDiff(oldValue, newValue, context = 3) {
|
|
|
1893
1893
|
"",
|
|
1894
1894
|
{ context }
|
|
1895
1895
|
);
|
|
1896
|
-
|
|
1896
|
+
const lines = patch.split("\n");
|
|
1897
|
+
const filtered = lines.filter((line) => {
|
|
1898
|
+
if (!line) return false;
|
|
1899
|
+
if (line === "\") return false;
|
|
1900
|
+
if (line.startsWith("@@")) return false;
|
|
1901
|
+
if (line.startsWith("---") || line.startsWith("+++")) return false;
|
|
1902
|
+
if (line.startsWith(" ")) return false;
|
|
1903
|
+
if (line.startsWith("+") || line.startsWith("-")) return true;
|
|
1904
|
+
return false;
|
|
1905
|
+
});
|
|
1906
|
+
while (filtered.length && filtered[0].trim() === "") filtered.shift();
|
|
1907
|
+
while (filtered.length && filtered[filtered.length - 1].trim() === "")
|
|
1908
|
+
filtered.pop();
|
|
1909
|
+
return filtered.join("\n");
|
|
1897
1910
|
}
|
|
1898
1911
|
function formatObjectDiff(oldValue, newValue) {
|
|
1899
1912
|
const differences = deepDiffExports.diff(oldValue, newValue) || [];
|
|
@@ -2006,19 +2019,22 @@ function stringify(value) {
|
|
|
2006
2019
|
function useStateChangeTracking(currentState, tx_id, options) {
|
|
2007
2020
|
const defaultFeedbackHandler = useDefaultFeedbackHandler();
|
|
2008
2021
|
const feedbackHandler = options?.onFeedback || defaultFeedbackHandler;
|
|
2009
|
-
const debounceMs = options?.debounceMs ??
|
|
2022
|
+
const debounceMs = options?.debounceMs ?? 3e3;
|
|
2010
2023
|
const diffType = options?.diffType ?? "git";
|
|
2011
2024
|
const compareWith = options?.compareWith;
|
|
2012
2025
|
const defaultTriggerName = options?.default_trigger_name ?? "auto_state_change";
|
|
2013
2026
|
const ignoreInitialNullish = options?.ignoreInitialNullish ?? true;
|
|
2014
2027
|
const prevStateRef = useRef(currentState);
|
|
2015
|
-
const changeStartStateRef = useRef(currentState);
|
|
2016
2028
|
const isFirstRenderRef = useRef(true);
|
|
2017
2029
|
const initialStateRef = useRef(currentState);
|
|
2030
|
+
const initialWasNullishRef = useRef(currentState == null);
|
|
2018
2031
|
const hasHadNonNullishStateRef = useRef(
|
|
2019
2032
|
currentState != null
|
|
2020
2033
|
// != null catches both null and undefined
|
|
2021
2034
|
);
|
|
2035
|
+
const hasEligibleBaselineRef = useRef(
|
|
2036
|
+
!(ignoreInitialNullish && currentState == null)
|
|
2037
|
+
);
|
|
2022
2038
|
const timeoutRef = useRef(null);
|
|
2023
2039
|
const currentTriggerNameRef = useRef(void 0);
|
|
2024
2040
|
const sendFeedback = useCallback(
|
|
@@ -2053,7 +2069,7 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2053
2069
|
const newTriggerName = trigger_name || defaultTriggerName;
|
|
2054
2070
|
if (timeoutRef.current && currentTriggerNameRef.current && currentTriggerNameRef.current !== newTriggerName) {
|
|
2055
2071
|
clearTimeout(timeoutRef.current);
|
|
2056
|
-
const startState =
|
|
2072
|
+
const startState = initialStateRef.current;
|
|
2057
2073
|
const currentStateBeforeChange = currentState;
|
|
2058
2074
|
sendFeedback(
|
|
2059
2075
|
startState,
|
|
@@ -2070,39 +2086,41 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2070
2086
|
if (isFirstRenderRef.current) {
|
|
2071
2087
|
isFirstRenderRef.current = false;
|
|
2072
2088
|
prevStateRef.current = currentState;
|
|
2073
|
-
|
|
2089
|
+
if (!ignoreInitialNullish || currentState != null) {
|
|
2090
|
+
initialStateRef.current = currentState;
|
|
2091
|
+
hasEligibleBaselineRef.current = true;
|
|
2092
|
+
}
|
|
2074
2093
|
return;
|
|
2075
2094
|
}
|
|
2076
2095
|
const prevState = prevStateRef.current;
|
|
2077
2096
|
const isEqual = compareWith ? compareWith(prevState, currentState) : JSON.stringify(prevState) === JSON.stringify(currentState);
|
|
2078
2097
|
if (!isEqual) {
|
|
2079
|
-
const shouldIgnoreChange = ignoreInitialNullish &&
|
|
2098
|
+
const shouldIgnoreChange = ignoreInitialNullish && initialWasNullishRef.current && // True initial state was nullish
|
|
2080
2099
|
!hasHadNonNullishStateRef.current && // We haven't had non-nullish state before
|
|
2081
2100
|
currentState != null;
|
|
2082
2101
|
if (currentState != null) {
|
|
2083
2102
|
hasHadNonNullishStateRef.current = true;
|
|
2084
2103
|
}
|
|
2085
2104
|
if (shouldIgnoreChange) {
|
|
2105
|
+
initialStateRef.current = currentState;
|
|
2106
|
+
hasEligibleBaselineRef.current = true;
|
|
2086
2107
|
prevStateRef.current = currentState;
|
|
2087
|
-
changeStartStateRef.current = currentState;
|
|
2088
2108
|
return;
|
|
2089
2109
|
}
|
|
2090
|
-
if (!timeoutRef.current) {
|
|
2091
|
-
changeStartStateRef.current = prevState;
|
|
2092
|
-
}
|
|
2093
2110
|
if (timeoutRef.current) {
|
|
2094
2111
|
clearTimeout(timeoutRef.current);
|
|
2095
2112
|
}
|
|
2096
2113
|
prevStateRef.current = currentState;
|
|
2097
2114
|
timeoutRef.current = setTimeout(() => {
|
|
2098
|
-
const startState =
|
|
2115
|
+
const startState = initialStateRef.current;
|
|
2099
2116
|
const finalState = currentState;
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2117
|
+
if (hasEligibleBaselineRef.current) {
|
|
2118
|
+
sendFeedback(
|
|
2119
|
+
startState,
|
|
2120
|
+
finalState,
|
|
2121
|
+
currentTriggerNameRef.current || defaultTriggerName
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2106
2124
|
timeoutRef.current = null;
|
|
2107
2125
|
}, debounceMs);
|
|
2108
2126
|
}
|
|
@@ -2142,43 +2160,42 @@ function useFeedbackState(initialState, tx_id, options) {
|
|
|
2142
2160
|
);
|
|
2143
2161
|
return [state, setState];
|
|
2144
2162
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2163
|
+
const _loadOtelApi = () => {
|
|
2164
|
+
const g = globalThis;
|
|
2165
|
+
const store = g[Symbol.for("opentelemetry.js.api.1")];
|
|
2166
|
+
const api = store?.api ?? store;
|
|
2167
|
+
if (!api?.context || !api?.propagation) {
|
|
2149
2168
|
throw new Error(
|
|
2150
|
-
"
|
|
2169
|
+
"@opentelemetry/api not found. Install it and configure a tracer provider."
|
|
2151
2170
|
);
|
|
2152
2171
|
}
|
|
2172
|
+
return api;
|
|
2153
2173
|
};
|
|
2154
|
-
function
|
|
2155
|
-
const { trace, context } = _loadOtelApi();
|
|
2174
|
+
function getTraceParent(_ = null) {
|
|
2156
2175
|
try {
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
if (activeSpan) {
|
|
2163
|
-
const spanCtx = activeSpan.spanContext();
|
|
2164
|
-
if (spanCtx.traceId) {
|
|
2165
|
-
return spanCtx.traceId;
|
|
2176
|
+
const { context, propagation } = _loadOtelApi();
|
|
2177
|
+
const carrier = {};
|
|
2178
|
+
propagation.inject(context.active(), carrier, {
|
|
2179
|
+
set: (c, k, v) => {
|
|
2180
|
+
c[k] = v;
|
|
2166
2181
|
}
|
|
2182
|
+
});
|
|
2183
|
+
const traceparent = carrier["traceparent"];
|
|
2184
|
+
if (!traceparent) {
|
|
2185
|
+
throw new Error("traceparent header not available from active context");
|
|
2167
2186
|
}
|
|
2187
|
+
return traceparent;
|
|
2168
2188
|
} catch (error) {
|
|
2169
2189
|
throw new Error(
|
|
2170
|
-
`Failed to extract
|
|
2190
|
+
`Failed to extract traceparent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2171
2191
|
);
|
|
2172
2192
|
}
|
|
2173
|
-
throw new Error(
|
|
2174
|
-
"OpenTelemetry trace ID not available. Ensure XHR/Fetch instrumentation is active and a request is in progress."
|
|
2175
|
-
);
|
|
2176
2193
|
}
|
|
2177
2194
|
export {
|
|
2178
2195
|
KeletContext,
|
|
2179
2196
|
KeletProvider,
|
|
2180
2197
|
VoteFeedback,
|
|
2181
|
-
|
|
2198
|
+
getTraceParent,
|
|
2182
2199
|
useDefaultFeedbackHandler,
|
|
2183
2200
|
useFeedbackState,
|
|
2184
2201
|
useKelet
|