LuxoAILuxoAIdocs
Welcome
InvoiceAgentInternalSupplyAgentBetaLearnAgentBeta
OverviewAgent Corev0.11.0StableObservabilityv0.6.0StableLuxo Assistantv0.6.0BetaFeedback UIv0.2.0BetaLuxo MLScaffold
API Console
All systemsv0.8.1
LuxoAILuxoAIdocs
WelcomeApiFeedback Ui

@luxoai-dev/feedback-ui · API Reference

Feedback UI.

Betav0.2.0

Two exports: a drop-in FeedbackWidget tied to a request id, and the underlying useFeedback hook for custom UIs. Both submit payloads that @luxoai-dev/agent-core/feedback validates server-side. An optional opinionated stylesheet ships under /styles.css for hosts that don’t want to design the widget from scratch.

At a glance
Package
@luxoai-dev/feedback-ui
Version
v0.2.0
Modules
. · /styles.css
Peers
react 18+ · @luxoai-dev/agent-core 0.7+
Default endpoint
POST /api/feedback

Public exports

Both exports submit to the same endpoint. The widget is the fast path; the hook lets you build a custom UI (radio dial, custom thank-you state, side-panel form, etc.).

import { FeedbackWidget, useFeedback } from "@luxoai-dev/feedback-ui";
FeedbackWidgetuseFeedbackFeedbackWidgetPropsUseFeedbackOptionsUseFeedbackResultSubmitFeedbackInput
Component

FeedbackWidget

Beta
function FeedbackWidget(props: FeedbackWidgetProps): JSX.Element
Drop-in 1-5 rating form tied to a request id. Renders data-feedback-state attributes (ready / error / submitted) for styling. POSTs the submission to the configured endpoint.
Parameters
ParamTypeRequiredDescription
props.requestIdstringRequiredThe LLM call's gateway requestId. Server-side validation requires this.
props.endpointstringOptionalSubmit URL. Defaults to '/api/feedback'.
props.fetchImpltypeof fetchOptionalCustom fetch (for testing or SSR).
props.onSuccess(entry: FeedbackEntry) => voidOptionalCalled after a successful submission with the validated entry returned by the server.
props.onError(error: Error) => voidOptionalCalled when validation or network fails.
props.promptLabelstringOptionalLabel above the rating row. Defaults to 'How was this answer?'.
props.thanksLabelstringOptionalReplacement content shown after submit. Defaults to 'Thanks for the feedback.'.
props.commentPlaceholderstringOptionalPlaceholder for the optional comment textarea.
props.submitLabelstringOptionalSubmit button label. Defaults to 'Send'.
props.showCommentbooleanOptionalShow the comment textarea. Defaults to true.
props.classNamestringOptionalForwarded to the root form element.
props.styleCSSPropertiesOptionalForwarded to the root form element.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
import { FeedbackWidget } from "@luxoai-dev/feedback-ui";

export function TriageFeedback({ requestId }: { requestId: string }) {
  return (
    <FeedbackWidget
      requestId={requestId}
      promptLabel="Was this triage useful?"
      submitLabel="Submit feedback"
      onSuccess={(entry) => console.log("recorded", entry.requestId)}
      onError={(err) => console.error(err)}
    />
  );
}

Server side

Wire /api/feedback to @luxoai-dev/agent-core/feedback: validateFeedback the body, recordFeedback from @luxoai-dev/observability for metrics, then write to createSupabaseFeedbackSink (or your own sink).
Hook

useFeedback

Beta
function useFeedback(options?: UseFeedbackOptions): UseFeedbackResult
Headless hook that submits validated payloads to the feedback endpoint. Tracks submitting / submitted / error state. Use this when you need a custom UI.
Parameters
ParamTypeRequiredDescription
options.endpointstringOptionalSubmit URL. Defaults to '/api/feedback'.
options.fetchImpltypeof fetchOptionalCustom fetch implementation.
options.onSuccess(entry: FeedbackEntry) => voidOptionalCalled with the validated entry on success.
options.onError(error: Error) => voidOptionalCalled when submission fails.

Returns

UseFeedbackResult

Object with submit(input), submitting, submitted, error, reset().
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"use client";
import { useFeedback } from "@luxoai-dev/feedback-ui";

export function CustomFeedbackBar({ requestId }: { requestId: string }) {
  const { submit, submitting, submitted, error, reset } = useFeedback();

  if (submitted) {
    return (
      <div>
        <span>Thanks!</span>
        <button onClick={reset}>Submit another</button>
      </div>
    );
  }

  return (
    <div>
      {[1, 2, 3, 4, 5].map((score) => (
        <button
          key={score}
          disabled={submitting}
          onClick={() => submit({ requestId, score: score as 1 | 2 | 3 | 4 | 5 })}
        >
          {score}★
        </button>
      ))}
      {error ? <p role="alert">{error.message}</p> : null}
    </div>
  );
}
interface

FeedbackWidgetProps

interface FeedbackWidgetProps extends UseFeedbackOptions {
  requestId: string;
  className?: string;
  style?: CSSProperties;
  promptLabel?: string;
  thanksLabel?: string;
  commentPlaceholder?: string;
  submitLabel?: string;
  showComment?: boolean;
}
Extends UseFeedbackOptions with display-only props. The widget always renders requestId, score buttons, optional comment, and a submit button — text labels are configurable, the structure is not.
interface

UseFeedbackOptions

interface UseFeedbackOptions {
  endpoint?: string;
  fetchImpl?: typeof fetch;
  onSuccess?: (entry: FeedbackEntry) => void;
  onError?: (error: Error) => void;
}
interface

UseFeedbackResult

interface UseFeedbackResult {
  submit: (input: SubmitFeedbackInput) => Promise<void>;
  submitting: boolean;
  submitted: boolean;
  error: Error | null;
  reset: () => void;
}
Fields
FieldTypeRequiredDescription
submit(input) => Promise<void>RequiredSubmit a feedback payload. Validation happens server-side; this resolves on success or sets error on failure.
submittingbooleanRequiredTrue while a request is in-flight.
submittedbooleanRequiredTrue after a successful submit. Stays true until reset() is called.
errorError | nullRequiredLast error from submit(), or null.
reset() => voidRequiredClear submitted + error so a new submission can run.
interface

SubmitFeedbackInput

interface SubmitFeedbackInput {
  requestId: string;
  score: 1 | 2 | 3 | 4 | 5;
  comment?: string;
  tags?: string[];
  metadata?: Record<string, unknown>;
}
Payload submitted to the endpoint. Mirrors the agent-core FeedbackEntry shape minus the server-set fields (userId, tenantId, createdAt — those are added on the server).

Optional stylesheet · /styles.css

Added in v0.2.0. A minimal opinionated stylesheet for FeedbackWidget — star row, comment textarea, submit button, error, and submitted states. Every element is selected by its data-feedback-* attribute so hosts can override individual rules without specificity wars. Includes a prefers-color-scheme: dark block for dark hosts that don’t pass a custom palette.

import "@luxoai-dev/feedback-ui/styles.css";
Constant

@luxoai-dev/feedback-ui/styles.css

since v0.2Beta
/* opinionated stylesheet, attribute-scoped */
[data-feedback-widget]    { /* root form */ }
[data-feedback-stars]     { /* 1-5 star row */ }
[data-feedback-star]      { /* individual star button */ }
[data-feedback-comment]   { /* optional textarea */ }
[data-feedback-submit]    { /* submit button */ }
[data-feedback-state="error"]     { /* error state */ }
[data-feedback-state="submitted"] { /* thank-you state */ }
@media (prefers-color-scheme: dark) { /* dark overrides */ }
Import once in a client component or the root layout. The widget already emits the matching data-feedback-* attributes — no additional props required. Skip this import to style the widget yourself; the component still works without it.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
// app/layout.tsx (or any client component that renders FeedbackWidget)
import "@luxoai-dev/feedback-ui/styles.css";

// Override a single rule without removing the rest:
// app/globals.css
[data-feedback-star][aria-pressed="true"] {
  color: var(--brand-yellow);
}

End-to-end loop

Pair the widget with the gateway’s requestId — show the widget under any LLM response, store the rating in your sink, surface aggregate scores via recordFeedback metrics. That’s the production loop the rest of the runtime expects.