@kelet-ai/feedback-ui 1.1.3 → 1.2.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 CHANGED
@@ -23,16 +23,16 @@ npx shadcn add https://feedback-ui.kelet.ai/r/vote-feedback.json
23
23
  ```
24
24
 
25
25
  ```tsx
26
- import { ShadcnVoteFeedback } from '@/components/ui/vote-feedback';
26
+ import { ShadcnVoteFeedback } from "@/components/ui/vote-feedback"
27
27
 
28
28
  function App() {
29
29
  return (
30
30
  <ShadcnVoteFeedback
31
31
  session_id="my-feature"
32
- onFeedback={feedback => console.log(feedback)}
32
+ onFeedback={(feedback) => console.log(feedback)}
33
33
  variant="outline"
34
34
  />
35
- );
35
+ )
36
36
  }
37
37
  ```
38
38
 
@@ -89,16 +89,16 @@ npm install @kelet-ai/feedback-ui
89
89
  Users explicitly vote and provide comments:
90
90
 
91
91
  ```tsx
92
- import { VoteFeedback } from '@kelet-ai/feedback-ui';
92
+ import { VoteFeedback } from "@kelet-ai/feedback-ui"
93
93
 
94
- <VoteFeedback.Root onFeedback={handleFeedback} session_id="ai-response">
94
+ ;<VoteFeedback.Root onFeedback={handleFeedback} session_id="ai-response">
95
95
  <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
96
96
  <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
97
97
  <VoteFeedback.Popover>
98
98
  <VoteFeedback.Textarea placeholder="How can we improve?" />
99
99
  <VoteFeedback.SubmitButton>Send feedback</VoteFeedback.SubmitButton>
100
100
  </VoteFeedback.Popover>
101
- </VoteFeedback.Root>;
101
+ </VoteFeedback.Root>
102
102
  ```
103
103
 
104
104
  ### **Pattern 2: Implicit State Tracking**
@@ -106,42 +106,79 @@ import { VoteFeedback } from '@kelet-ai/feedback-ui';
106
106
  Capture user behavior automatically by using a drop-in replacement for useState:
107
107
 
108
108
  ```tsx
109
- import { useFeedbackState } from '@kelet-ai/feedback-ui';
109
+ import { useFeedbackState } from "@kelet-ai/feedback-ui"
110
110
 
111
111
  function ContentEditor() {
112
112
  // Drop-in useState replacement that tracks changes
113
113
  const [content, setContent] = useFeedbackState(
114
- 'Start writing...',
115
- 'content-editor'
116
- );
114
+ "Start writing...",
115
+ "content-editor"
116
+ )
117
117
 
118
118
  return (
119
119
  <textarea
120
120
  value={content}
121
- onChange={e => setContent(e.target.value)}
121
+ onChange={(e) => setContent(e.target.value)}
122
122
  placeholder="Edit this content..."
123
123
  />
124
- );
124
+ )
125
125
  // 🎯 Automatically sends feedback when user stops editing!
126
126
  }
127
127
  ```
128
128
 
129
+ ### **Pattern 4: Manual Signal Sending**
130
+
131
+ Send signals directly without tying them to component state — useful for custom events, page views, or any interaction not covered by the other patterns:
132
+
133
+ ```tsx
134
+ import { KeletProvider, useKeletSignal } from "@kelet-ai/feedback-ui"
135
+
136
+ function CopyButton({ sessionId }: { sessionId: string }) {
137
+ const sendSignal = useKeletSignal()
138
+
139
+ return (
140
+ <button
141
+ onClick={() => {
142
+ navigator.clipboard.writeText("...")
143
+ sendSignal({
144
+ session_id: sessionId,
145
+ kind: "feedback",
146
+ source: "human",
147
+ score: 1,
148
+ trigger_name: "copy_button_clicked",
149
+ })
150
+ }}
151
+ >
152
+ Copy
153
+ </button>
154
+ )
155
+ }
156
+
157
+ function App() {
158
+ return (
159
+ <KeletProvider apiKey="..." project="my-project">
160
+ <CopyButton sessionId="response-abc123" />
161
+ </KeletProvider>
162
+ )
163
+ }
164
+ ```
165
+
129
166
  ### **Pattern 3: Complex State with Reducer**
130
167
 
131
168
  For advanced state management with automatic trigger tracking, by using a drop-in replacement for useReducer:
132
169
 
133
170
  ```tsx
134
- import { useFeedbackReducer } from '@kelet-ai/feedback-ui';
171
+ import { useFeedbackReducer } from "@kelet-ai/feedback-ui"
135
172
 
136
173
  function TodoApp() {
137
- const [todos, dispatch] = useFeedbackReducer(todoReducer, [], 'todo-app');
174
+ const [todos, dispatch] = useFeedbackReducer(todoReducer, [], "todo-app")
138
175
 
139
176
  return (
140
- <button onClick={() => dispatch({ type: 'ADD_TODO', text: 'New task' })}>
177
+ <button onClick={() => dispatch({ type: "ADD_TODO", text: "New task" })}>
141
178
  Add Todo
142
179
  </button>
143
180
  // 🎯 Automatically sends feedback with trigger_name: 'ADD_TODO'
144
- );
181
+ )
145
182
  }
146
183
  ```
147
184
 
@@ -198,15 +235,15 @@ Controls how feedback is collected and what data is captured:
198
235
  ```tsx
199
236
  // Implicit feedback - tracks changes automatically
200
237
  const [content, setContent] = useFeedbackState(
201
- 'Initial content',
202
- 'content-editor'
203
- );
238
+ "Initial content",
239
+ "content-editor"
240
+ )
204
241
  // Sends feedback when user stops editing
205
242
 
206
243
  // Loading pattern - no noise generated
207
- const [user, setUser] = useFeedbackState(null, 'user-data');
208
- setUser(userData); // ❌ No feedback sent (ignoreInitialNullish: true)
209
- setUser(updatedUser); // ✅ Feedback sent for real changes
244
+ const [user, setUser] = useFeedbackState(null, "user-data")
245
+ setUser(userData) // ❌ No feedback sent (ignoreInitialNullish: true)
246
+ setUser(updatedUser) // ✅ Feedback sent for real changes
210
247
  ```
211
248
 
212
249
  ### **🏷️ Trigger Names**
@@ -224,16 +261,16 @@ Categorization system for grouping and analyzing feedback:
224
261
 
225
262
  ```tsx
226
263
  // Content creation triggers
227
- 'user_typing' | 'ai_generation' | 'spell_check' | 'auto_format';
264
+ ;"user_typing" | "ai_generation" | "spell_check" | "auto_format"
228
265
 
229
266
  // User interaction triggers
230
- 'manual_edit' | 'voice_input' | 'copy_paste' | 'drag_drop';
267
+ ;"manual_edit" | "voice_input" | "copy_paste" | "drag_drop"
231
268
 
232
269
  // Workflow triggers
233
- 'draft' | 'review' | 'approval' | 'publication';
270
+ ;"draft" | "review" | "approval" | "publication"
234
271
 
235
272
  // AI interaction triggers
236
- 'ai_completion' | 'ai_correction' | 'prompt_result';
273
+ ;"ai_completion" | "ai_correction" | "prompt_result"
237
274
  ```
238
275
 
239
276
  #### **Dynamic Trigger Names**
@@ -241,14 +278,14 @@ Categorization system for grouping and analyzing feedback:
241
278
  ```tsx
242
279
  const [document, setDocument] = useFeedbackState(
243
280
  initialDoc,
244
- 'document-editor',
245
- { default_trigger_name: 'user_edit' }
246
- );
281
+ "document-editor",
282
+ { default_trigger_name: "user_edit" }
283
+ )
247
284
 
248
285
  // Different triggers for different actions
249
- setDocument(aiGeneratedContent, 'ai_generation');
250
- setDocument(userEditedContent, 'manual_refinement');
251
- setDocument(spellCheckedContent, 'spell_check');
286
+ setDocument(aiGeneratedContent, "ai_generation")
287
+ setDocument(userEditedContent, "manual_refinement")
288
+ setDocument(spellCheckedContent, "spell_check")
252
289
  ```
253
290
 
254
291
  ### **🔄 State Change Tracking**
@@ -260,9 +297,9 @@ For implicit feedback, understanding how state changes are processed:
260
297
  ```tsx
261
298
  // User types: "Hello" → "Hello World" → "Hello World!"
262
299
  // Only sends ONE feedback after user stops typing
263
- const [text, setText] = useFeedbackState('', 'editor', {
300
+ const [text, setText] = useFeedbackState("", "editor", {
264
301
  debounceMs: 3000, // Wait 3s after last change
265
- });
302
+ })
266
303
  ```
267
304
 
268
305
  #### **Diff Calculation**
@@ -281,14 +318,14 @@ Automatic classification of changes:
281
318
 
282
319
  ```tsx
283
320
  // Smart vote logic based on change magnitude
284
- const [data, setData] = useFeedbackState(initial, 'tracker', {
321
+ const [data, setData] = useFeedbackState(initial, "tracker", {
285
322
  vote: (before, after, diffPercentage) => {
286
323
  // Small changes = refinement (positive)
287
- if (diffPercentage <= 0.5) return 'upvote';
324
+ if (diffPercentage <= 0.5) return "upvote"
288
325
  // Large changes = major revision (might indicate issues)
289
- return 'downvote';
326
+ return "downvote"
290
327
  },
291
- });
328
+ })
292
329
  ```
293
330
 
294
331
  ### **🎯 Best Practices Summary**
@@ -330,7 +367,7 @@ Main container component that manages feedback state.
330
367
  session_id="unique-id" // Required: Unique tracking ID
331
368
  onFeedback={handleFeedback} // Required: Callback function
332
369
  trigger_name="user_feedback" // Optional: Categorization
333
- extra_metadata={{ page: 'home' }} // Optional: Additional data
370
+ extra_metadata={{ page: "home" }} // Optional: Additional data
334
371
  >
335
372
  {/* Child components */}
336
373
  </VoteFeedback.Root>
@@ -359,6 +396,39 @@ Context-aware feedback form that appears after voting.
359
396
 
360
397
  ---
361
398
 
399
+ ## ✉️ Manual Signal Sending
400
+
401
+ ### **useKeletSignal Hook**
402
+
403
+ Returns the signal-sending function from the nearest `KeletProvider`. Use this to send signals manually for any event — custom interactions, page views, feature usage — without tying them to component state.
404
+
405
+ Safe to call outside a provider: returns a no-op and logs a warning.
406
+
407
+ ```tsx
408
+ import { useKeletSignal } from "@kelet-ai/feedback-ui"
409
+
410
+ function MyComponent() {
411
+ const sendSignal = useKeletSignal()
412
+
413
+ const handleAction = () => {
414
+ sendSignal({
415
+ session_id: "my-session",
416
+ kind: "feedback",
417
+ source: "human",
418
+ score: 1,
419
+ trigger_name: "user_action",
420
+ metadata: { context: "sidebar" },
421
+ })
422
+ }
423
+
424
+ return <button onClick={handleAction}>Do something</button>
425
+ }
426
+ ```
427
+
428
+ Requires a `KeletProvider` ancestor to send signals to the API.
429
+
430
+ ---
431
+
362
432
  ## 📊 Automatic Feedback Hooks
363
433
 
364
434
  ### **useFeedbackState Hook**
@@ -368,39 +438,39 @@ A **drop-in replacement for React's useState** that automatically tracks state c
368
438
  #### **Basic Usage**
369
439
 
370
440
  ```tsx
371
- const [count, setCount] = useFeedbackState(0, 'counter-widget');
441
+ const [count, setCount] = useFeedbackState(0, "counter-widget")
372
442
  ```
373
443
 
374
444
  #### **Advanced Configuration**
375
445
 
376
446
  ```tsx
377
447
  const [profile, setProfile] = useFeedbackState(
378
- { name: '', email: '' },
379
- state => `profile-${state.email}`, // Dynamic session_id
448
+ { name: "", email: "" },
449
+ (state) => `profile-${state.email}`, // Dynamic session_id
380
450
  {
381
451
  debounceMs: 2000, // Wait time before sending feedback
382
- diffType: 'object', // Format: 'git' | 'object' | 'json'
383
- metadata: { component: 'UserProfile' },
384
- vote: 'upvote', // Static vote or custom function
452
+ diffType: "object", // Format: 'git' | 'object' | 'json'
453
+ metadata: { component: "UserProfile" },
454
+ vote: "upvote", // Static vote or custom function
385
455
  }
386
- );
456
+ )
387
457
  ```
388
458
 
389
459
  #### **Trigger Names for Categorization**
390
460
 
391
461
  ```tsx
392
462
  const [content, setContent] = useFeedbackState(
393
- 'Initial content',
394
- 'content-editor',
395
- { default_trigger_name: 'manual_edit' }
396
- );
463
+ "Initial content",
464
+ "content-editor",
465
+ { default_trigger_name: "manual_edit" }
466
+ )
397
467
 
398
468
  // Uses default trigger
399
- setContent('User typed this');
469
+ setContent("User typed this")
400
470
 
401
471
  // Override with specific trigger
402
- setContent('AI generated this', 'ai_assistance');
403
- setContent('Spell checker fixed this', 'spell_check');
472
+ setContent("AI generated this", "ai_assistance")
473
+ setContent("Spell checker fixed this", "spell_check")
404
474
  ```
405
475
 
406
476
  ### **useFeedbackReducer Hook**
@@ -411,12 +481,12 @@ A **drop-in replacement for React's useReducer** with automatic trigger name ext
411
481
  const [state, dispatch] = useFeedbackReducer(
412
482
  counterReducer,
413
483
  { count: 0 },
414
- 'counter-widget'
415
- );
484
+ "counter-widget"
485
+ )
416
486
 
417
- dispatch({ type: 'increment' }); // trigger_name: 'increment'
418
- dispatch({ type: 'reset' }); // trigger_name: 'reset'
419
- dispatch({ type: 'custom' }, 'override'); // Custom trigger name
487
+ dispatch({ type: "increment" }) // trigger_name: 'increment'
488
+ dispatch({ type: "reset" }) // trigger_name: 'reset'
489
+ dispatch({ type: "custom" }, "override") // Custom trigger name
420
490
  ```
421
491
 
422
492
  ---
@@ -426,7 +496,7 @@ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
426
496
  ### **Basic Voting Interface**
427
497
 
428
498
  ```tsx
429
- <VoteFeedback.Root onFeedback={feedback => console.log(feedback)}>
499
+ <VoteFeedback.Root onFeedback={(feedback) => console.log(feedback)}>
430
500
  <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
431
501
  <VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
432
502
  </VoteFeedback.Root>
@@ -470,7 +540,7 @@ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
470
540
  onFeedback={handleFeedback}
471
541
  trigger_name="ai_evaluation"
472
542
  extra_metadata={{
473
- model: 'gpt-4',
543
+ model: "gpt-4",
474
544
  prompt_length: 150,
475
545
  response_time: 1200,
476
546
  }}
@@ -492,14 +562,14 @@ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
492
562
 
493
563
  ```typescript
494
564
  interface FeedbackData {
495
- session_id: string; // Unique tracking ID
496
- vote: 'upvote' | 'downvote'; // User's vote
497
- explanation?: string; // Optional user comment
498
- extra_metadata?: Record<string, any>; // Additional context data
499
- source?: 'IMPLICIT' | 'EXPLICIT'; // How feedback was collected
500
- correction?: string; // For implicit feedback diffs
501
- selection?: string; // Selected text context
502
- trigger_name?: string; // Categorization tag
565
+ session_id: string // Unique tracking ID
566
+ vote: "upvote" | "downvote" // User's vote
567
+ explanation?: string // Optional user comment
568
+ extra_metadata?: Record<string, any> // Additional context data
569
+ source?: "IMPLICIT" | "EXPLICIT" // How feedback was collected
570
+ correction?: string // For implicit feedback diffs
571
+ selection?: string // Selected text context
572
+ trigger_name?: string // Categorization tag
503
573
  }
504
574
  ```
505
575
 
@@ -615,9 +685,9 @@ bun run checks # Run all quality checks (lint, format, typecheck, tests)
615
685
 
616
686
  ```tsx
617
687
  // ✅ Ensure debounce time has passed(the time, not the configuration! :P) and state actually changed
618
- const [value, setValue] = useFeedbackState('initial', 'test', {
688
+ const [value, setValue] = useFeedbackState("initial", "test", {
619
689
  debounceMs: 1000, // Wait 1 second after last change
620
- });
690
+ })
621
691
  ```
622
692
 
623
693
  #### **Q: Styling not working**
@@ -1,6 +1,6 @@
1
1
  import { DownvoteButtonProps, PopoverProps, SubmitButtonProps, TextareaProps, UpvoteButtonProps, VoteFeedbackRootProps } from '../types';
2
2
  export declare const VoteFeedback: {
3
- Root: ({ children, onFeedback, defaultText, session_id: sessionIdProp, extra_metadata, trigger_name, }: VoteFeedbackRootProps) => import("react/jsx-runtime").JSX.Element;
3
+ Root: ({ children, onFeedback, defaultText, session_id: sessionIdProp, metadata, trigger_name: triggerProp, trace_id, }: VoteFeedbackRootProps) => import("react/jsx-runtime").JSX.Element;
4
4
  UpvoteButton: ({ asChild, children, onClick, ...props }: UpvoteButtonProps) => import("react/jsx-runtime").JSX.Element;
5
5
  DownvoteButton: ({ asChild, children, onClick, ...props }: DownvoteButtonProps) => string | number | bigint | true | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element;
6
6
  Popover: ({ asChild, children, ...props }: PopoverProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -12,6 +12,26 @@ interface KeletProviderProps {
12
12
  }
13
13
  export declare const KeletContext: React.Context<KeletContextValue | null>;
14
14
  export declare const useKelet: () => KeletContextValue;
15
+ /**
16
+ * Returns the signal-sending function from the nearest KeletProvider.
17
+ *
18
+ * Use this hook to send signals manually — e.g. tracking custom events,
19
+ * page views, or any interaction not covered by useFeedbackState.
20
+ *
21
+ * Safe to call outside a provider: returns a no-op and logs a warning.
22
+ *
23
+ * @example
24
+ * const sendSignal = useKeletSignal()
25
+ *
26
+ * sendSignal({
27
+ * session_id: 'my-session',
28
+ * kind: 'feedback',
29
+ * source: 'human',
30
+ * score: 1,
31
+ * trigger_name: 'copy_button_clicked',
32
+ * })
33
+ */
34
+ export declare const useKeletSignal: () => ((data: FeedbackData) => Promise<void>);
15
35
  export declare const useDefaultFeedbackHandler: () => ((data: FeedbackData) => Promise<void>);
16
36
  export declare const KeletProvider: React.FC<React.PropsWithChildren<KeletProviderProps>>;
17
37
  export {};